Merge 2 array objects into 1 - mongodb

I am trying to merge objects within an array based on another array. What I have is,
Orders
{
_id: 0,
orderID: 1,
entries: [
{ item_id: 43, quantity: 2 },
{ item_id: 2, quantity: 1}
]
}
Items
{
_id: 43,
item_name: "tshirt"
}
{
_id: 2,
item_name: "jeans"
}
After lookup I am getting the below document with 2 arrays - entries and items. I would like to have entries contain the corresponding item.
{
_id: 0,
orderID: 1,
entries: [
{ item_id: 43, quantity: 2 },
{ item_id: 2, quantity: 1}
]
items: [
{ item_id: 43, item_name: "tshirt" },
{ item_id: 2, item_name: "jeans" },
]
}
Desired Output:
{
_id: 0,
orderID: 1,
entries: [
{ item_id: 43, quantity: 2, item_name: "tshirt" },
{ item_id: 2, quantity: 1, item_name: "jeans"}
]
}
I was able to achieve this by unwinding both arrays, addFields and then grouping as mentioned by #whoami.
See current pipeline: https://mongoplayground.net/p/rL-Lzmfuw9h
Is there any way to achieve this without unwinding?

Related

mongoDB filter, sort, and rank result

I have a collection like this
{
id: 1,
category: "food",
score: 20
}
{
id: 2,
category: "drink",
score: 19
}
{
id: 3,
category: "food",
score: 50
}
{
id: 4,
category: "food",
score: 30
}
id is not unique btw.
but it is unique in that category.
so it is possible to have
{id: 1, category: "food"}
{id: 1, category: "drink"}
but not
{id: 1, category: "food"}
{id: 1, category: "food"}
here's what I want to do
find all category == "food"
-> it will give id: 1, 3, 4
// I can add some other filter here before sort happen
// like id less than 100
sort them by score
-> it will give id: 3, 4, 1 // highest score must be the first entry
then what is the rank of id: [4, 1]
-> it should give me {id: 4, rank: 2}, {id: 1, rank: 3}
how can I achieve this?
please give me some snippets or idea
db.collection.aggregate([
{
"$match": { //Filter conditions
"category": "food"
}
},
{
"$sort": {//Sorting
"score": -1
}
},
{
"$group": { //Group by null to get array index
"_id": "null",
"data": {
"$push": "$$ROOT",
}
}
},
{
"$unwind": { //Unwind and get index
path: "$data",
"includeArrayIndex": "index"
}
},
{
"$match": {
"data.id": { //Filter require ids
$in: [
3,
4
]
}
}
}
])
Sample

How do I get the last object array in MongoDB based on condition

I want to query mongoDB Data:
mongoDBData:
[{ cost: 1, productCode: "A"}],
[{ cost: 2, productCode: "A"}],
[{ cost: 3, productCode: "B"}],
[{ cost: 4, productCode: "A"}],
[{ cost: 5, productCode: "B"}],
[{ cost: 6, productCode: "A"}],
[{ cost: 7, productCode: "C"}],
[{ cost: 8, productCode: "C"}],
[{ cost: 9, productCode: "D"}],
[{ cost: 10, productCode: "D"}]
based on an array. This is the array:
mappedProductCode = ["A", "B", "C"]
This is my desired result:
desiredResult = [
{productCode: "A", cost: 6},
{productCode: "B", cost: 5},
{productCode: "C", cost: 8},
]
Here's how I did by making a loop:
productCost=[]
for (let i = 0; i < mappedProductCode.length; i++) {
const skuLoop = mappedProductCode[i];
const skuCost = await PosCost.findOne({ productCOde: mappedProductCode[i] }).sort({ _id: -1 }).limit(1);
const loopPrice = skuCost? skuCost.cost : 0;
productCost[i] = {
sku: skuLoop,
cost: loopPrice
};
}
My desired result is still achieved but it is very slow, what do I do to improve the code?
Maybe something like this:
db.collection.aggregate([
{
$match: {
productCode: {
$in: [
"A",
"B",
"C"
]
}
}
},
{
$sort: {
productCode: 1,
cost: -1
}
},
{
$group: {
_id: "$productCode",
cost: {
$first: "$cost"
}
}
},
{
$project: {
productCode: "$_id",
cost: 1,
_id: 0
}
},
{
$sort: {
productCode: 1
}
}
])
Explained:
$match the products that you need
$sort by productCode and descending cost
$group by productCode and get only the first cost from the descending order
$project the _id to the original key name "productCode"
$sort by needed final result order
playground

Java MongoDB Projection

I am referring mongodb official page for projection where I came across following example where elements of array in subdocument is filtered:
https://docs.mongodb.com/manual/reference/operator/aggregation/filter/#exp._S_filter
db.sales.aggregate([
{
$project: {
items: {
$filter: {
input: "$items",
as: "item",
cond: { $gte: [ "$$item.price", 100 ] }
}
}
}
}
])
I am trying to implement this in Java but I am not doing it correctly and elements in subdocument array are not filtered.
Input Collection:
{
_id: 0,
items: [
{ item_id: 43, quantity: 2, price: 10 },
{ item_id: 2, quantity: 1, price: 240 }
]
}
{
_id: 1,
items: [
{ item_id: 23, quantity: 3, price: 110 },
{ item_id: 103, quantity: 4, price: 5 },
{ item_id: 38, quantity: 1, price: 300 }
]
}
{
_id: 2,
items: [
{ item_id: 4, quantity: 1, price: 23 }
]
}
Expected Output Collection:
{
"_id" : 0,
"items" : [
{ "item_id" : 2, "quantity" : 1, "price" : 240 }
]
}
{
"_id" : 1,
"items" : [
{ "item_id" : 23, "quantity" : 3, "price" : 110 },
{ "item_id" : 38, "quantity" : 1, "price" : 300 }
]
}
{ "_id" : 2, "items" : [ ] }
In Java(mongo Driver 3.9.1), this is what I am doing:
Bson priceFilter = Filters.gte("items.price", 100);
mongoCollection.aggregate(
Aggregates.project(Projections.fields(priceFilter))
);
How do I project with aggregate function for the subdocument arrays where I need to filter out elements from subdocument array based on some condition?
In MongoDB Java Driver 3.9.1, collection.aggregate() takes a java.util.List as parameter. So you need to replace your Java code with the below.
mongoCollection.aggregate(
Arrays.asList(
Aggregates.project(Projections.computed("items",
new Document().append("$filter",
new Document().append("input", "$items").append("as", "item").append("cond",
new Document().append("$gte", Arrays.asList("$$item.price",100))))))
)
);

MongoDB: $filter multiple arrays

I have an collection like:
{
_id: 0,
items: [
{ item_id: 43, quantity: 2, price: 10 },
{ item_id: 2, quantity: 1, price: 240 }
],
T: [
{ item_id: 2993, quantity: 3, price: 110 },
{ item_id: 90103, quantity: 4, price: 5 },
{ item_id: 398, quantity: 1, price: 300 }
]
}
{
_id: 1,
items: [
{ item_id: 23, quantity: 3, price: 110 },
{ item_id: 103, quantity: 4, price: 5 },
{ item_id: 38, quantity: 1, price: 300 }
],
T: [
{ item_id: 23, quantity: 3, price: 110 },
{ item_id: 103, quantity: 4, price: 5 },
{ item_id: 38, quantity: 1, price: 300 }
]
}
{
_id: 2,
items: [
{ item_id: 4, quantity: 1, price: 23 }
],
T: [
{ item_id: 203, quantity: 3, price: 110 },
{ item_id: 003, quantity: 4, price: 5 },
{ item_id: 398, quantity: 1, price: 300 }
]
}
I want to return a all the items in the items array with a price >= 100. That is done with the following:
$project: {
items: {
$filter: {
input: "$items",
as: "item",
cond: { $gte: [ "$$item.price", 100 ] }
}
}
}
How can I expand this expression to $filter on the items array and the T array all elements that have a price >= 100?
You can include both the items and T fields in the same $project, each with its own $filter:
db.test.aggregate([
{$project: {
items: {
$filter: {
input: "$items",
as: "item",
cond: { $gte: [ "$$item.price", 100 ] }
}
},
T: {
$filter: {
input: "$T",
as: "t",
cond: { $gte: [ "$$t.price", 100 ] }
}
}
}}
])

Order Mongoose/MongoDB query results by number of values not in an array

I have two collections with the following simplified schemas:
// Ingredient
{
_id: Number
}
// Recipe
{
_id: Number,
ingredients: [{
type: Number,
ref: 'Ingredient'
}]
}
I'm trying to figure out how to implement a search query for recipes based on what ingredients you have available, sorted by the number of ingredients missing from each recipe.
For example, if I have the following data:
// Ingredients
{
_id: 1
},
{
_id: 2
},
{
_id: 3
},
{
_id: 4
},
{
_id: 5
}
// Recipes
{
_id: 1,
ingredients: [1, 2, 5]
},
{
_id: 2,
ingredients: [2, 4]
},
{
_id: 3,
ingredients: [2, 3]
}
and I input ingredients 2 and 3, the expected results would be
{
_id: 3,
ingredients: [2, 3] // Missing 0 ingredients
},
{
_id: 2,
ingredients: [2, 4] // Missing 1 ingredient
},
{
_id: 1,
ingredients: [1, 2, 5] // Missing 2 ingredients
}
Is it possible to do this with a query alone?
You can do this using $setDifference to find the missing ingredients, and then $size to get their count that you can then $sort on.
var ingredients = [2, 3];
db.recipes.aggregate([
{$project: {missing: {$setDifference: ['$ingredients', ingredients]}}},
{$project: {missing: 1, numMissing: {$size: '$missing'}}},
{$sort: {numMissing: 1}}
])
Results:
{ "_id" : 3, "missing" : [ ], "numMissing" : 0 }
{ "_id" : 2, "missing" : [ 4 ], "numMissing" : 1 }
{ "_id" : 1, "missing" : [ 1, 5 ], "numMissing" : 2 }