🍃 MongoDB Tutorial

MongoDB $group Accumulators: $sum, $avg, $min, $max, $push & $addToSet

Learn every MongoDB $group accumulator operator — $sum, $count, $avg, $min, $max, $first, $last, $push and $addToSet — with real aggregation examples.

$group Accumulators

Accumulators are the expressions you place inside $group to actually compute something for each group — sums, averages, extremes, and even arrays of the grouped values.

Accumulator Description
$sum Adds up numeric values across a group
$count Counts documents in a group
$avg Averages a field's value across a group
$min / $max Finds the smallest / largest value
$first / $last Grabs the first / last document's value in the group
$push Collects values into an array (allows duplicates)
$addToSet Collects values into an array (no duplicates)

$sum

db.employees.aggregate([
  { $group: { _id: "$city", countcity: { $sum: 1 } } }
])
// $sum: 1 is the classic "count per group" pattern

db.employees.aggregate([
  { $match: { salary: { $gte: 50000 } } },
  { $group: { _id: "$age", countcity: { $sum: 1 } } }
])

$count

db.employees.aggregate([{ $count: "age" }])
// { age: 6 }  -- top-level count of all documents (labeled "age" here, name is arbitrary)

db.employees.aggregate([
  { $group: { _id: "$age", count: { $count: {} } } }
])
// { _id: 24, count: 1 }
// { _id: 22, count: 2 }

$avg

db.employees.aggregate([
  { $group: { _id: "$age", avgsalary: { $avg: "$salary" } } },
  { $sort: { avgsalary: -1 } }
])

db.employees.aggregate([
  { $group: { _id: "$age", avgsalary: { $avg: "$salary" } } },
  { $match: { avgsalary: { $gte: 50000 } } },
  { $sort: { avgsalary: -1 } }
])
// you CAN $match again after a $group, to filter on the computed values

$min / $max

db.employees.aggregate([
  { $group: { _id: null, minsalary: { $min: "$salary" } } }
])
// _id: null groups the ENTIRE collection into one group

db.employees.aggregate([
  { $group: { _id: null, maxsalary: { $max: "$salary" } } },
  { $project: { _id: 0 } }
])

$first / $last

Returns the first (or last) document's value within the order the pipeline received them — usually paired with $match to scope things down first.

db.employees.aggregate([
  { $match: { city: "Mumbai" } },
  { $group: { _id: null, first: { $first: "$age" } } }
])

db.employees.aggregate([
  { $match: { city: "Mumbai" } },
  { $group: { _id: null, last: { $last: "$age" } } }
])

$push — Collect Into an Array (With Duplicates)

db.employees.aggregate([
  {
    $group: {
      _id: null,
      basicdetail: { $push: { name: "$name", email: "$email" } }
    }
  }
])
// builds one array containing a {name, email} object per document

$addToSet — Collect Into an Array (No Duplicates)

db.employees.aggregate([
  { $group: { _id: null, city: { $addToSet: "$city" } } }
])
// { _id: null, city: [ 'Mumbai', 'Palghar', 'Pune' ] }

Common Errors & Troubleshooting

  • $sum/$avg producing 0/null unexpectedly — check the field actually holds a number in every document; a string value in a numeric field is silently ignored by these accumulators rather than causing an error.
  • $first/$last returning inconsistent results across runs — without a $sort stage before $group, the order documents arrive in isn't guaranteed — always sort first if you depend on which document is "first" or "last."
  • $push blowing past the 16MB document size limit — if a group has an enormous number of documents, pushing every one of them into a single array can exceed MongoDB's document size limit; consider $limiting what you collect, or restructuring the query.

Key Takeaways

  • _id: null inside $group treats the whole collection as a single group — perfect for overall totals/averages.
  • You can $match again after a $group to filter on the computed accumulator values.
  • $push keeps duplicates; $addToSet doesn't — pick based on whether you need a "list" or a "set".