Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

MongoDB nested lookup with 3 levels

I need to retrieve the entire single object hierarchy from the database as a JSON. Actually, the proposal about any other solution to achieve this result would be highly appreciated. I decided to use MongoDB with its $lookup support.

So I have three collections:

party

{ "_id" : "2", "name" : "party2" } { "_id" : "5", "name" : "party5" } { "_id" : "4", "name" : "party4" } { "_id" : "1", "name" : "party1" } { "_id" : "3", "name" : "party3" }     

address

{ "_id" : "a3", "street" : "Address3", "party_id" : "2" } { "_id" : "a6", "street" : "Address6", "party_id" : "5" } { "_id" : "a1", "street" : "Address1", "party_id" : "1" } { "_id" : "a5", "street" : "Address5", "party_id" : "5" } { "_id" : "a2", "street" : "Address2", "party_id" : "1" } { "_id" : "a4", "street" : "Address4", "party_id" : "3" } 

addressComment

{ "_id" : "ac2", "address_id" : "a1", "comment" : "Comment2" } { "_id" : "ac1", "address_id" : "a1", "comment" : "Comment1" } { "_id" : "ac5", "address_id" : "a5", "comment" : "Comment6" } { "_id" : "ac4", "address_id" : "a3", "comment" : "Comment4" } { "_id" : "ac3", "address_id" : "a2", "comment" : "Comment3" } 

I need to retrieve all parties with all corresponding addresses and address comments as part of the record. My aggregation:

db.party.aggregate([{     $lookup: {         from: "address",         localField: "_id",         foreignField: "party_id",         as: "address"     } }, {     $unwind: "$address" }, {     $lookup: {         from: "addressComment",         localField: "address._id",         foreignField: "address_id",         as: "address.addressComment"     } }]) 

The result is pretty weird. Some records are ok. But Party with _id: 4 is missing (there is no address for it). Also, there are two Party _id: 1 in the result set (but with different addresses):

{     "_id": "1",     "name": "party1",     "address": {         "_id": "2",         "street": "Address2",         "party_id": "1",         "addressComment": [{             "_id": "3",             "address_id": "2",             "comment": "Comment3"         }]     } }{     "_id": "1",     "name": "party1",     "address": {         "_id": "1",         "street": "Address1",         "party_id": "1",         "addressComment": [{             "_id": "1",             "address_id": "1",             "comment": "Comment1"         },         {             "_id": "2",             "address_id": "1",             "comment": "Comment2"         }]     } }{     "_id": "3",     "name": "party3",     "address": {         "_id": "4",         "street": "Address4",         "party_id": "3",         "addressComment": []     } }{     "_id": "5",     "name": "party5",     "address": {         "_id": "5",         "street": "Address5",         "party_id": "5",         "addressComment": [{             "_id": "5",             "address_id": "5",             "comment": "Comment5"         }]     } }{     "_id": "2",     "name": "party2",     "address": {         "_id": "3",         "street": "Address3",         "party_id": "2",         "addressComment": [{             "_id": "4",             "address_id": "3",             "comment": "Comment4"         }]     } } 

Please help me with this. I'm pretty new to MongoDB but I feel it can do what I need from it.

like image 906
Yuriy Avatar asked Mar 15 '16 18:03

Yuriy


People also ask

Can we join 2 collections in MongoDB?

For performing MongoDB Join two collections, you must use the $lookup operator. It is defined as a stage that executes a left outer join with another collection and aids in filtering data from joined documents. For example, if a user requires all grades from all students, then the below query can be written: Students.

What is $lookup in MongoDB?

The $lookup operator is an aggregation operator or an aggregation stage, which is used to join a document from one collection to a document of another collection of the same database based on some queries. Both the collections should belong to the same databases.

Does MongoDB have aggregation?

In MongoDB, aggregation operations process the data records/documents and return computed results. It collects values from various documents and groups them together and then performs different types of operations on that grouped data like sum, average, minimum, maximum, etc to return a computed result.


2 Answers

The cause of your 'troubles' is the second aggregation stage - { $unwind: "$address" }. It removes record for party with _id: 4 (because its address array is empty, as you mention) and produces two records for parties _id: 1 and _id: 5 (because each of them has two addresses).

  • To prevent removing of parties without addresses you should set preserveNullAndEmptyArrays option of $unwind stage to true.

  • To prevent duplicating of parties for its different addresses you should add $group aggregation stage to your pipeline. Also, use $project stage with $filter operator to exclude empty address records in output.

db.party.aggregate([{   $lookup: {     from: "address",     localField: "_id",     foreignField: "party_id",     as: "address"   } }, {   $unwind: {     path: "$address",     preserveNullAndEmptyArrays: true   } }, {   $lookup: {     from: "addressComment",     localField: "address._id",     foreignField: "address_id",     as: "address.addressComment",   } }, {   $group: {     _id : "$_id",     name: { $first: "$name" },     address: { $push: "$address" }   } }, {   $project: {     _id: 1,     name: 1,     address: {       $filter: { input: "$address", as: "a", cond: { $ifNull: ["$$a._id", false] } }     }    } }]); 
like image 81
Shad Avatar answered Sep 28 '22 10:09

Shad


With the mongodb 3.6 and above $lookup syntax it is quite simple to join nested fields without using $unwind.

db.party.aggregate([   { "$lookup": {     "from": "address",     "let": { "partyId": "$_id" },     "pipeline": [       { "$match": { "$expr": { "$eq": ["$party_id", "$$partyId"] }}},       { "$lookup": {         "from": "addressComment",         "let": { "addressId": "$_id" },         "pipeline": [           { "$match": { "$expr": { "$eq": ["$address_id", "$$addressId"] }}}         ],         "as": "address"       }}     ],     "as": "address"   }},   { "$unwind": "$address" } ]) 
like image 39
Ashh Avatar answered Sep 28 '22 08:09

Ashh