Search code examples
node.jsdatabasemongodbaggregation-frameworkmongodb-lookup

Mongodb lookup array of elements with combined result


So these are my two documents

Order document:

{
   "_id":"02a33b9a-284c-4869-885e-d46981fdd679",
   "context":{
      "products":[
         {
         "id": "e68fc86a-b4ad-4588-b182-ae9ee3db25e4",
         "version": "2020-03-14T13:18:41.296+00:00"
         }
      ],
   },
}

Product document:

{
   "_id":"e68fc86a-b4ad-4588-b182-ae9ee3db25e4",
   "context":{
      "name": "My Product",
      "image": "someimage"
   },
}

So I'm trying to do a lookup for a products in order document, but the result should contain combined fields, like so:

"products":[
             {
             "_id": "e68fc86a-b4ad-4588-b182-ae9ee3db25e4",
             "version": "2020-03-14T13:18:41.296+00:00",
             "name": "My Product",
             "image": "someimage"
             }
          ],

Not sure how to do this, should I do it outside of the lookup, or inside? This is my aggregation

Orders.aggregate([
{
   "$lookup":{
      "from":"products",
      "let":{
         "products":"$context.products"
      },
      "pipeline":[
         {
            "$match":{
               "$expr":{
                  "$in":[
                     "$_id",
                     "$$products.id"
                  ]
               }
            }
         },
         {
            "$project":{
               "_id":0,
               "id":1,
               "name":"$context.name"
            }
         }
      ],
      "as":"mergedProducts"
   }
},
{
   "$project":{
      "context":"$context",
      "mergedProducts":"$mergedProducts"
   }
},
]);

Solution

  • You need to run that mapping outside of $lookup by running $map along with $arrayElemAt to get single pair from both arrays and then apply $mergeObjects to get one object as a result:

    db.Order.aggregate([
        {
            $lookup: {
                from: "products",
                localField: "context.products.id",
                foreignField: "_id",
                as: "productDetails"
            }
        },
        {
            $addFields: {
                productDetails: {
                    $map: {
                        input: "$productDetails",
                        in: {
                            _id: "$$this._id",
                            name: "$$this.context.name"
                        }
                    }
                }
            }
        },
        {
            $project: {
                _id: 1,
                "context.products": {
                    $map: {
                        input: "$context.products",
                        as: "prod",
                        in: {
                            $mergeObjects: [
                                "$$prod",
                                { $arrayElemAt: [ { $filter: { input: "$productDetails", cond: { $eq: [ "$$this._id", "$$prod.id" ] } } }, 0 ] }
                            ]
                        }
                    }
                }
            }
        }
    ])
    

    Mongo Playground

    The goals of the last step is to take take two arrays: products and productDetails (the output of $lookup) and find matches between them. We know there's always one match so we can get only one item $arrayElemAt 0. As an output of $map there will be single array containing "merged" documents.