How do I sum up all the sizes of an array property and support a missing array in Mongodb?

Viewed 73

I'm trying to get a grand total of all tags.

    let topics = await ReadSchema.aggregate([{
      $group: {
          "_id": "$id",
          count: { $size: { "$ifNull": [ "$summary.topics", [] ] } }
      }
    }]);


I get the error: server error MongoError: unknown group operator '$size'

Bonus points if you can remove duplicate "topics" in the total.

2 Answers

It can't allow $size as accumulator operator,

The $group's field Computed using the accumulator operators. The operator must be one of the following accumulator operators are: $accumulator, $addToSet, $avg, $first, $last, $max, $mergeObjects, $min, $push, $stdDevPop, $stdDevSamp, $sum, for more details refer $group,

  • use $sum before $size operator,
let topics = await ReadSchema.aggregate([
    {
        $group: {
            "_id": "$id",
            count: { 
                $sum: {
                    $size: { 
                        "$ifNull": [ "$summary.topics", [] ] 
                    }
                }
            }
        }
    }
]);

Remove duplicate topics in the total:

  • $addToSet to topics, make unique array of topics array
  • $reduce to iterate loop of topics array and get union of all topics tags using $setUnion and, $size to get total count of unique topics
let topics = await ReadSchema.aggregate([
  {
    $group: {
      _id: "$id",
      topics: {
        $addToSet: { $ifNull: [ "$summary.topics", [] ] }
      }
    }
  },
  {
    $project: {
      id: 1,
      count: {
        $size: {
          $reduce: {
            input: "$topics",
            initialValue: [],
            in: { $setUnion: ["$$this", "$$value"] }
          }
        }
      }
    }
  }
])

Suggestions:

  • match topics is array condition in first stage in your query, $type: 4 indicates topics field has array data type or not, this will filter your documents before $group stage and you do not longer need to check $ifNull condition in $group stage, you can remove that condition.
  • for query optimization you can put index on summary.topics field.
  {
    $match: {
      $and: [
        { "summary.topics": { $type: 4 } },
        { "summary.topics": { $ne: [] } }
      ]
    }
  }

I found a faster way to remove duplicates and count size of an embedded array document.

    let topics = await ReadSchema.aggregate([
      {
        $project: {
          _id: '0',
          topics: { $ifNull: ['$summary.topics', []] },
        },
      },
      { $unwind: '$topics' },
      { $group: { _id: '0', topics: { $addToSet: '$topics' } } },
      {
        $project: {
          count: { $size: '$topics' },
        },
      },
    ]);

First we create a projection with just the topics. It will be an array of objects which contain the topics array for each document, we use $ifNull which will default to an empty array for documents where the embedded summary.topics array is missing.

We then $unwind that array of arrays into one flat array. Then $group the array using $addToSet which will implicitly remove duplicates by its nature.

We then $project a new document with a count property that takes the $size of the new array (as duplicates are now removed).

Related