Search code examples
node.jsmongodbmongooseaggregation

Mongoose Aggregation of a field that optionally exists in mongoose - rating calculation


My product document looks thus:

{
  "_id": {
    "$oid": "60999af1160b0eebed51f203"
  },
  "name": "Olive Skin care On1",
  "business": {
    "$oid": "609fa1d25adc157a33c59098"
  },
    "ratings": [{
    "_id": {
      "$oid": "60bdb541d6212ec44e62273c"
    },
    "user": {
      "$oid": "5fdce4bd75dbe4864fcd5001"
    },
    "rating": 5
  }]
}

I have this mongoose query to get product details alongside the product rating. Some products have ratings field while others do not. When I make a query as shown here, it returns a response as expected with calculated average rating. The response looks thus:

[
  {
      "_id": "609a657f2bf43c290fb22df8",
      "name": "Olive babay Oil",
      "business": "6079ed084d9ab0c3171317ea",
      "averageRating": 5
  }
]

Here is the query:

const productArray = await Product.aggregate([
    {
        $match: {
            _id: mongooseProductId,
        },
    },
    { $unwind: "$ratings" },
    {
        $project: {
            averageRating: { $avg: "$ratings.rating" },
            name: 1,
            business: 1,
        },
    },
]);

However if the same product above is modified by removing the ratings field, the query below will return an empty array. How do I write my query to ensure that whether the ratings field exists or not, I do not get an empty array provided that the matching criteria is met.

Meaning that I can get an expected response like this when the ratings field doesn't exist on my product document:

[
  {
      "_id": "609a657f2bf43c290fb22df8",
      "name": "Olive babay Oil",
      "business": "6079ed084d9ab0c3171317ea",
      "averageRating": null
  }
]

And this when the rating field exists:

[
  {
      "_id": "609a657f2bf43c290fb22df8",
      "name": "Olive babay Oil",
      "business": "6079ed084d9ab0c3171317ea",
      "averageRating": 5
  }
]


Solution

  • Based on @turivishal's comment. The query below solved the problem.

    const productArray = await Product.aggregate([
        {
            $match: {
                _id: mongooseProductId,
            },
        },
        { $unwind:{ path: "$ratings", preserveNullAndEmptyArrays: true } },
        {
            $project: {
                averageRating: { $avg: "$ratings.rating" },
                name: 1,
                business: 1,
            },
        },
    ]);