MongoDB Aggregations

MongoDB's aggregation pipeline lets you transform and analyze documents by passing them through a sequence of processing stages, each reshaping the data before it reaches the next.

What Is the Aggregation Pipeline?

Every aggregation operation in MongoDB is built around a pipeline: an ordered array of stages that documents flow through one at a time. Each stage takes the output of the previous stage as its input, applies some transformation such as filtering, grouping, or reshaping, and passes the result along. Because the stages run in sequence, the order you place them in matters enormously for both correctness and performance.

You invoke a pipeline with db.collection.aggregate(), passing an array of stage documents. Each stage is itself a document whose single key names the operator, for example $match, $group, $project, $sort, $limit, or $lookup. There is no limit on how many stages you can chain, though MongoDB does impose internal memory limits per stage unless you enable disk usage for large sorts and groups.

Why Not Just Use find()?

The find() method is excellent for retrieving documents that match a query, and it supports projections and simple sorting. But it cannot reshape documents, compute new fields from existing ones, join data across collections, or calculate aggregate statistics like sums, averages, or counts grouped by a key. The aggregation pipeline was built precisely to fill that gap, effectively giving you a data-processing language inside the database itself.

A Minimal Pipeline

db.orders.aggregate([
  { $match: { status: "completed" } },
  { $sort: { orderDate: -1 } },
  { $limit: 5 }
])

This pipeline filters orders down to completed ones, sorts the survivors by date descending, and returns only the five most recent. Each stage narrows or reorders the working set before handing it to the next.

Combining Multiple Stage Types

Grouping and Reshaping Together

db.sales.aggregate([
  { $match: { region: "APAC" } },
  { $group: {
      _id: "$product",
      totalRevenue: { $sum: "$amount" },
      orders: { $sum: 1 }
  } },
  { $sort: { totalRevenue: -1 } },
  { $project: {
      _id: 0,
      product: "$_id",
      totalRevenue: 1,
      orders: 1
  } }
])

Here $match trims the input to a single region, $group computes per-product revenue and order counts, $sort ranks products by revenue, and $project renames and cleans up the final output shape. Notice how each stage builds directly on the field names produced by the one before it.

Joining a Related Collection

db.orders.aggregate([
  { $match: { status: "shipped" } },
  { $lookup: {
      from: "customers",
      localField: "customerId",
      foreignField: "_id",
      as: "customerInfo"
  } },
  { $limit: 10 }
])
StagePurpose
$matchFilters documents, similar to a find() query
$groupGroups documents by a key and computes aggregates
$projectReshapes documents, adding, removing, or renaming fields
$sortOrders documents by one or more fields
$limitCaps the number of documents passed downstream
$lookupPerforms a left outer join against another collection
Note: Place $match and $limit as early as possible in your pipeline. Filtering out documents before expensive stages like $group or $lookup run means MongoDB has far less data to process at every subsequent step.
Note: Stages execute strictly in the order you write them. Sorting after you have already discarded fields with $project can fail silently if the sort key no longer exists in the reshaped documents.

Exercise: MongoDB Aggregations

How does data move through an aggregation pipeline?