I need to get 0 from $sum operator when array is empty inside a group aggregation

aggregate([
                {$unwind: '$sizes'},
                {
                    $group: {
                        _id: "$_id",
                        brand: {$first: '$brand'},
                        title: {$first: '$title'},
                        totalShoes: {
                            $sum: {
                 
                                $cond: [{$eq: ["$sizes", []]},  0, "$sizes.count"]
                            }
                        },
                    }
                },
            ])

As you can see I use $cond inside the $sum operator and true case returns 0, but this causes $sum to skip the model from the list. I need to get something like {title: Falcon, totalShoes: 0, sizes: []} when sizes array is empty.

Here is model example.

{
            "_id": "61dc771dc825e9bb0066a20a",
            "title": "Falcon",
            "brand": "Adidas",
            "sizes": [
                {
                    "_id": "5fbf9730f2192b42589f63b1",
                    "sizeValue": 41,
                    "count": 1
                },
                {
                    "_id": "5fbf9730f2192b42589f63b2",
                    "sizeValue": 44,
                    "count": 4
                }
            ]
        },
  
{
            "_id": "61fabd7d9e38f5770f4f52e5",
            "title": "568",
            "brand": "New Balance",
            "sizes": []
        },
1 Like

The major issue with your aggregation is that after $unwind, sizes is not not an array anymore. See at the examples in the documentation.

An $unwind followed by $group with _id:$_id is usually seen as bad and in most cases, $reduce, $map or $filter can be used.

There is an option for $unwind for your empty array. It is preserveNullAndEmptyArrays.

In your case a simple $reduce with input:$sizes, initialValue:0 and in:{ $add:[$$value,$$this.count]} should be working. But I have not tested.

3 Likes

You don’t have to use $unwind since it’s an expensive operation. You can do the following:

  • $map - to map sizes object array to it’s count sub-property. The output would be an array of numbers.
  • $sum - to sum the whole array generated above.
  • $set - to put the result in totalShoes field.
db.collection.aggregate([
  {
    "$set": {
      "totalShoes": {
        "$sum": {
          "$map": {
            "input": "$sizes",
            "in": "$$this.count"
          }
        }
      }
    }
  }
])

Working example

3 Likes

Thank you!
For someone who needs there is working example.

aggregate([
                {
                    $project: {
                        _id: 1,
                        title: 1,
                        brand: 1,
                        sex: 1,
                        totalShoes: {
                            $reduce: {
                                input: "$sizes",
                                initialValue: 0,
                                in: {$add: ["$$value", "$$this.count"]}
                            }
                        }
                    }
                }
            ]);
2 Likes

Cheers. Also working solution.
Who needs working example look at this.

aggregate([
                {
                    $set: {
                        totalShoes: {
                            $sum: {
                                $map: {
                                    "input": "$sizes",
                                    "in": "$$this.count"
                                }
                            }
                        }
                    },
                },
                {
                    $project: {
                        _id: 1,
                        title: 1,
                        brand: 1,
                        sex: 1,
                        totalShoes: 1
                    },
                }
            ]);
1 Like

This topic was automatically closed 5 days after the last reply. New replies are no longer allowed.