🍃 MongoDB Tutorial

MongoDB $group Stage: Grouping Documents in Aggregation

Learn how MongoDB's $group aggregation stage groups documents by a field value, and how to combine it with $match to group only a filtered subset.

The $group Stage

$group is where MongoDB aggregation starts feeling like SQL's GROUP BY. It collapses documents that share the same value for a chosen field into a single output document per group.

Syntax

db.collection.aggregate([
  { $match: { field: value } },   // optional
  { $group: { _id: "$field" } }
])

Grouping by a Field

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

This is essentially distinct("city"), but expressed as a pipeline — and it becomes far more powerful once you add accumulators (covered in the next lesson).

db.employees.aggregate([
  { $group: { _id: "$reportinmgr.mgrid" } }
])
// grouping also works on embedded fields

Counting Documents Per Group

Add an accumulator field alongside _id to compute something for each group — $sum: 1 counts how many documents fell into each one:

db.employees.aggregate([
  { $group: { _id: "$city", countcity: { $sum: 1 } } }
])
// { _id: 'Mumbai', countcity: 3 }
// { _id: 'Pune',   countcity: 3 }
// { _id: 'Palghar', countcity: 3 }

Grouping a Filtered Subset

Add $match before $group to only group documents that meet a condition first:

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

Common Errors & Troubleshooting

  • Confusing $group's _id with the document's real _id — inside a $group stage, _id refers to the grouping key you chose, not the original document identifier — the original _id field is not carried through unless you explicitly collect it with $push/$first.
  • Grouping on a field that doesn't exist in some documents — documents missing the grouping field get grouped together under _id: null, which can silently merge unrelated "missing data" documents into one bucket.
  • Expecting $group to preserve document order — without a $sort before it, $group doesn't guarantee which document's values end up in $first/$last — always $sort first if the "first"/"last" document matters.

Key Takeaways

  • $group's _id defines what counts as "the same group" — don't confuse it with each document's original _id.
  • Without an accumulator, $group behaves like distinct().
  • Put $match before $group when you only want to summarize a subset of the collection.