The aggregation pipeline

MongoDB · Practical · 14 min

Reports, dashboards and any server-side data reshaping use aggregation instead of shipping documents to the app.

aggregate([stage, stage, ...]) passes documents through a pipeline of stages, each feeding the next, like a Unix pipe or chained array methods.

  • $match filters (same syntax as find); put it first so it can use indexes,
  • $group groups by _id and computes accumulators: $sum, $avg, $min, $max, $count, $push, $addToSet, $first,
  • $project or $set reshape documents and compute fields with expressions such as $multiply, $concat, $dateToString, $cond,
  • $sort, $limit, $skip, $count, $unwind and $lookup (next lessons).

Inside expressions, "$field" (a string starting with $) refers to a field's value, while plain strings are literals. Grouping by null produces a single total over all documents.

The optimizer reorders and merges stages where safe, for example moving a $match before a $project, but write pipelines in the efficient order anyway: filter early, project away big fields early, sort on indexed fields before grouping when possible.

Each stage may use up to 100 MB of RAM; larger sorts and groups spill to disk (allowed by default since MongoDB 6.0). $out and $merge write results to a collection, useful for materialized summaries.

Example

db.sales.insertMany([
  { region: "north", item: "pen", qty: 10, price: 1.5, day: ISODate("2026-10-01") },
  { region: "north", item: "pad", qty: 2,  price: 4,   day: ISODate("2026-10-01") },
  { region: "south", item: "pen", qty: 5,  price: 1.5, day: ISODate("2026-10-02") },
  { region: "south", item: "ink", qty: 1,  price: 9,   day: ISODate("2026-10-03") }
])
db.sales.aggregate([
  { $match: { day: { $gte: ISODate("2026-10-01") } } },
  { $set: { revenue: { $multiply: ["$qty", "$price"] } } },
  { $group: { _id: "$region", revenue: { $sum: "$revenue" }, orders: { $count: {} } } },
  { $sort: { revenue: -1 } }
])
// [ { _id: 'north', revenue: 23, orders: 2 },
//   { _id: 'south', revenue: 16.5, orders: 2 } ]

Remember

  • A pipeline is an array of stages; output of one is input to the next
  • "$field" references a value; $group needs an _id
  • $match early so indexes can be used
  • Stages have a 100 MB memory limit and spill to disk when allowed

Watch out for

  • Writing "qty" instead of "$qty" in an expression: you get the literal string, not the field value.

Check yourself

  1. In { $group: { _id: "$category", n: { $sum: 1 } } }, what is n?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: www.mongodb.com/docs/manual/core/aggregation-pipeline/