How to do mongodb inner join with nested array? - mongodb

Warehouses schema:
{_id: 1, name: 'A'}
{_id: 2, name: 'B'}
{_id: 3, name: 'C'}
Stocks schema:
{_id: 11, productId: 1, instock: [{warehouse: 'A', qty: 20}, {warehouse: 'B', qty: 5}, {warehouse: 'C', qty: 8}]
{_id: 12, productId: 2, instock: [{warehouse: 'A', qty: 30}]
I am new to MongoDB, but will like to have one row per record to show products' available qty in each of A,B,C warehouses:
Desired array output:
instock: [
{_id: 11, productId: 1, warehouse: 'A', qty: 20},
{_id: 11, productId: 1, warehouse: 'B', qty: 5},
{_id: 11, productId: 1, warehouse: 'C', qty: 8},
{_id: 12, productId: 2, warehouse: 'A', qty: 30},
{_id: 12, productId: 2, warehouse: 'B', qty: 0},
{_id: 12, productId: 2, warehouse: 'C', qty: 0}
]
I read about $lookup, $unwind, $project, and tried something like below but no where near to what I want:
Warehouse.aggregate([
{
$lookup:
{
from: "stocks",
pipeline: [
{ $project: { _id: 0, instock: {qty: 1, warehouse: 1} }},
{ $replaceRoot: { newRoot: { newStock : '$instock' } } }
],
as: "instock"
}
} ,
]);
hi, Anothony Winzlet, your advise works partially, for example:
{_id: 12, productId: 2, instock: [{warehouse: 'A', qty: 30}]
From your solution:
Result show only for warehouse A:
[{_id: 12, productId: 2, warehouse: 'A', qty: 30}]
Can I get for warehouse B & C as well? (will default qty to 0 if not defined)
[{_id: 12, productId: 2, warehouse: 'A', qty: 30},
{_id: 12, productId: 2, warehouse: 'B', qty: 0},
{_id: 12, productId: 2, warehouse: 'C', qty: 0}]
Not sure if above is possible to achieve ... thank you

Solution from Anthony Winzlet:
Warehouse.aggregate([
{ "$unwind": "$instock" },
{ "$replaceRoot": { "newRoot": { "$mergeObjects": ["$$ROOT", "$instock"] } }},
{ "$project": { "instock": 0 } }
])

Related

db.createView() by matching and comparing two tables in mongodb

I would like to createView by comparing and grouping two table in mongodb.
table1
{id: 1, item: 'apple', house: 'A'},
{id: 2, item: 'apple', house: 'A'},
{id: 3, item: 'apple', house: 'B'},
{id: 4, item: 'banana', house: 'B'},
{id: 5, item: 'banana', house: 'C'},
{id: 6, item: 'pear', house: 'A'},
{id: 7, item: 'pear', house: 'B'},
{id: 8, item: 'pear', house: 'A'},
{id: 9, item: 'pear', house: 'C'},
And in table 2, I need to compare and match
table2
{id: 1, fruits: 'apple', type: 'important'},
{id: 2, fruits: 'banana', type: 'important'},
{id: 3, fruits: 'pear', type: 'notImportant'},
The result I want to get:
houses
{id: 1, house: 'A', totalItem: 4, noOfImportant: 2 },
{id: 2, house: 'B', totalItem: 2, noOfImportant: 2},
{id: 3, house: 'C', totalItem: 2, noOfImportant: 1},
I have tried:
db.createView(
'houses',
'table1',
[
{$lookup: { from: 'table2', localField: 'fruits', foreignField: 'item', as: 'fruits'}},
{
'$group': {
house: '$house',
totalItem: {$sum:1},
noOfImportant: {$sum:'$table2.notImpotant'},
}
},
]
)
but I can't seem to get any thing out. please help me. Thank you very much in advance!
The $lookup stage will return an array, even if it only finds a single matching document.
You can use the $arrayElemAt operator to pick out the first element, or you could simply $unwind the result.
Either way, it looks like there was a typo in your group: noOfImportant: {$sum:'$table2.notImpotant'}, - there doesn't appear to be a field named "table2", I suspect you meant to check the type field of the fruits document.
The $group stage requires that there be an explicitly defined _id field, if you need that field to be called house you can to that afterward with projection.
Since the type field is not numeric, you would need to use $cond in order to count them up.
db.createView(
'houses',
'table1',
[
{$lookup: {
from: 'table2',
localField: 'item',
foreignField: 'fruits',
as: 'fruits'
}},
{$unwind: {
path: '$fruits',
preserveNullAndEmptyArrays: true
}},
{$group: {
_id: '$house',
totalItem: {$sum:1},
noOfImportant: {
$sum:{
$cond:{
if:{$eq:['$fruits.type', 'important']},
then: 1,
else: 0
}
}
},
}},
{$addFields: { house: "$_id" }},
{$project: { _id: 0 }}
]
)
The aggregate functions returns much more descriptive error messages. You should test the pipeline prior to creating the view.
Here is a MongoPlayground that shows this pipeline in action.

Merge 2 array objects into 1

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?

Mongodb : how to use aggregate to build standings on nested fields

This is my collection :
[
{userId: "u1", data: { score1: 1, score2: 2, score3: 3 }, day: 1},
{userId: "u1", data: { score1: 1, score2: 0, score3: 0 }, day: 2},
{userId: "u1", data: { score1: 5, score2: 3, score3: 2 }, day: 3},
{userId: "u2", data: { score1: 2, score2: 5, score3: 1 }, day: 1},
{userId: "u2", data: { score1: 1, score2: 1, score3: 6 }, day: 2},
{userId: "u2", data: { score1: 3, score2: 5, score3: 3 }, day: 3},
{userId: "u3", data: { score1: 4, score2: 1, score3: 1 }, day: 1},
{userId: "u3", data: { score1: 0, score2: 1, score3: 1 }, day: 2},
{userId: "u3", data: { score1: 0, score2: 1, score3: 10 }, day: 3}
]
I would like to build the following leaderboards tables :
{
score1: [
{"u1": 7}, // sum of all score1 for u1
{"u2": 6}, // sum of all score1 for u2
{"u3": 4}, // sum of all score1 for u3
],
score2: [
{"u2": 11}, // sum of all score2 for u2
{"u1": 5}, // sum of all score2 for u1
{"u3": 3}, // sum of all score2 for u3
],
score3: [
{"u3": 12}, // sum of all score3 for u3
{"u2": 10}, // sum of all score3 for u2
{"u1": 5}, // sum of all score3 for u1
],
}
So far I can group by userId and compute the aggregate of each score for the 3 of them :
db.myCollection.aggregate([
{
$group: {
_id: "$userId",
score1: { $sum: "$score1" },
score2: { $sum: "$score2" },
score3: { $sum: "$score3" }
}
}
])
Which gives me :
[
{
_id: "u1",
score1: 7,
score2: 5,
score3: 5
},
{
_id: "u2",
score1: 6,
score2: 11,
score3: 10
},
{
_id: "u3",
score1: 4,
score2: 3,
score3: 12
},
]
How can I extract each type of score and build their corresponding leaderboard ?
Thanks in advance.
I would first use $objectToArray on the data field and $unwind it so each document has 1 user and 1 score. Then group by userId and data.k (which will contain "score1", "score2", etc.) and compute sum. Then regroup by score name and push an object with k:userId, v:<score> to an array. Then group once more on null and push k:scoreName, v:<object with user scores> to an array. Finally $arrayToObject to convert that array to the object you want:
db.collection.aggregate([
{$addFields: {data: {$objectToArray: "$data"}}},
{$unwind: "$data"},
{$group: {
_id: {userId: "$userId", scoreName: "$data.k"},
score: {$sum:"$data.v"}
}},
{$group: {
_id:"$_id.scoreName",
data:{$push:{k:"$_id.userId", v:"$score"}}
}},
{$group: {
_id: null,
scores:{$push:{k:"$_id", v:{$arrayToObject:"$data"}}}
}},
{$replaceRoot:{newRoot:{$arrayToObject:"$scores"}}}
])
Playground

MongoDB $expr compare field in array

{
name: 'product A',
min_price: 5,
max_price: 15,
stores: [
{name: 'A', price: 6},
{name: 'B', price: 4}
]
},
{
name: 'product B',
min_price: 9,
max_price: 14,
stores: [
{name: 'C', price: 12},
{name: 'B', price: 10}
]
}
How can I find product have store's price $lt min_price?
I tried:
{$expr: { $lt: [ "$min_price", "$stores.price"] }}
Seems like I am doing it wrong!
You're close, you just need to add $min into the batch:
{$expr: { $lt: [ "$min_price", {$min: "$stores.price"} ] }}

mongo aggregate how to choose ‘$graphLookup or $lookup’

I don't know how to choose ‘$graphLookup or $lookup’ Other similar
I am looking forward to mongodb officially more complete documentation
example:
{parentId: 0, cid: 1, other: 'a'},
{parentId: 0, cid: 2, other: 'b'},
{parentId: 0, cid: 3, other: 'c'},
{parentId: 1, cid: 11, other: 'aa'},
{parentId: 2, cid: 12, other: 'ab'},
{parentId: 3, cid: 13, other: 'ac'},
result:
{
parentId: 0, cid: 1, other: 'a',
children: [
{parentId: 1, cid: 11, other: 'aa'},
]
},{
parentId: 0, cid: 2, other: 'b',
children: [
{parentId: 2, cid: 12, other: 'ab'},
]
},{
parentId: 0, cid: 3, other: 'c',
children: [
{parentId: 3, cid: 13, other: 'ac'},
]
}
},
how do?
You need to use $graphLookup
db.collection.aggregate([
{
$match: {
"parentId": {
$eq: 0
}
}
},
{
$graphLookup: {
from: "collection",
startWith: "$cid",
connectFromField: "cid",
connectToField: "parentId",
as: "children"
}
}
])
$lookup is used to “joined” collection for processing.