Aggregation Fundamentals
$group
Combine documents by a key. Totals, counts, averages — the stage interviews always probe.
$group is SQL GROUP BY. _id is the key. Every other field is an accumulator. Fields you do not accumulate are gone. There is no SELECT * after a group.
db.orders.aggregate([
{ $match: { status: { $ne: "cancelled" } } },
{
$group: {
_id: "$userId",
total: { $sum: "$amount" },
orders: { $sum: 1 }
}
}
]){ _id: ObjectId("64a1b2c3d4e5f6a7b8c90001"), total: 18400, orders: 4 }| Accumulator | Use |
|---|---|
| $sum | Totals; $sum: 1 to count |
| $avg | Averages |
| $max / $min | Extremes |
| $push / $addToSet | Collect values into an array |
| $first / $last | One value from the group — sort first if order matters |
db.orders.aggregate([
{ $group: { _id: null, revenue: { $sum: "$amount" } } }
])
db.users.aggregate([
{ $group: { _id: { city: "$city", role: "$role" }, n: { $sum: 1 } } }
])$group does not sort. Add $sort. _id: "$userId" is a field path — the $ is required. { _id: "userId" } would put the string userId on every group (one bucket). $first without a prior $sort is 'some document in the group', not 'earliest order'.
Interview question
How does $group work, and what does _id mean inside it?
_id is the grouping key — a field path, a compound object, or null for one bucket. Every other field is an accumulator. Fields you did not accumulate are not in the output. $group does not preserve order; sort afterwards.
Practice
$group, usually with a $match in front.