Search code examples
mongodbmongoosemongodb-queryaggregation-frameworkmongoose-populate

How to lookup a field in an array of subdocuments in mongoose?


I have an array of review objects like this :

   "reviews": {
        "author": "5e9167c5303a530023bcae42",
        "rate": 5,
        "spoiler": false,
        "content": "This is a comment This is a comment This is a comment.",
        "createdAt": "2020-04-12T16:08:34.966Z",
        "updatedAt": "2020-04-12T16:08:34.966Z"
    },

What I want to achieve is to lookup the author field and get the user data, but the problem is that the lookup I am trying to use only returns this to me:

Code :

 .lookup({
    from: 'users',
    localField: 'reviews.author',
    foreignField: '_id',
    as: 'reviews.author',
  })

Response :

Response of api

Any way to get the author's data in that field? That's where the author's Id is.


Solution

  • Try to execute below query on your database :

    db.reviews.aggregate([
      /** unwind in general is not needed for `$lookup` for if you wanted to match lookup result with specific elem in array is needed */
      {
        $unwind: { path: "$reviews", preserveNullAndEmptyArrays: true },
      },
      {
        $lookup: {
          from: "users",
          localField: "reviews.author",
          foreignField: "_id",
          as: "author", // Pull lookup result into 'author' field
        },
      },
      /** Update 'reviews.author' field in 'reviews' object by checking if   'author' field got a match from 'users' collection.
       * If Yes - As lookup returns an array get first elem & assign(As there will be only one element returned -uniques),
       * If No - keep 'reviews.author' as is */
      {
        $addFields: {
          "reviews.author": {
            $cond: [
              { $ne: ["$author", []] },
              { $arrayElemAt: ["$author", 0] },
              "$reviews.author",
            ],
          },
        },
      },
      /** Group back the documents based on '_id' field & push back all individual 'reviews' objects to 'reviews' array */
      {
        $group: {
          _id: "$_id",
          reviews: { $push: "$reviews" },
        },
      },
    ]);
    

    Test : MongoDB-Playground

    Note : Just in case if you've other fields in document along with reviews that needs to be preserved in output then starting at $group use these stages :

      {
        $group: {
          _id: "$_id",
          data: {
            $first: "$$ROOT"
          },
          reviews: {
            $push: "$reviews"
          }
        }
      },
      {
        $addFields: {
          "data.reviews": "$reviews"
        }
      },
      {
        $project: {
          "data.author": 0
        }
      },
      {
        $replaceRoot: {
          newRoot: "$data"
        }
      }
    

    Test : MongoDB-Playground

    Note : Try to keep queries to run on lesser datasets maybe by adding $match as first stage to filter documents & also have proper indexes.