MongoDB from Basics to Production · MongoDB Basics

The Aggregation Pipeline

Analyse data on the server with $match, $group, $project, $unwind, $sort and $facet stages, in the right order for performance.

Create a free account to save this page, take notes and track your progress. Sign in

What you'll build

Sales analytics on order documents: revenue per category, best-selling products, and a dashboard query that returns several summaries at once with $facet.

The idea

  • An aggregation pipeline is a list of stages; each transforms the stream of documents.
  • Common stages: $match (filter), $group (aggregate with $sum, $avg, $max...), $project/$set (reshape), $unwind (one document per array element), $sort, $limit, $lookup, $facet.
  • Put $match (and $sort + $limit) early, so indexes are used and later stages process fewer documents.

The code

Revenue, top products and a dashboard
db.orders.insertMany([
  { _id: 1, status: "paid", lines: [{ sku: "mug", category: "kitchen", qty: 2, price: 8.5 }, { sku: "tea", category: "food", qty: 3, price: 12 }] },
  { _id: 2, status: "paid", lines: [{ sku: "teapot", category: "kitchen", qty: 1, price: 24 }] },
  { _id: 3, status: "refunded", lines: [{ sku: "kettle", category: "appliances", qty: 1, price: 31.9 }] },
  { _id: 4, status: "paid", lines: [{ sku: "mug", category: "kitchen", qty: 4, price: 8.5 }] },
]);

print("revenue by category:");
db.orders.aggregate([
  { $match: { status: "paid" } },
  { $unwind: "$lines" },
  { $group: { _id: "$lines.category", revenue: { $sum: { $multiply: ["$lines.qty", "$lines.price"] } }, units: { $sum: "$lines.qty" } } },
  { $sort: { revenue: -1 } },
]).forEach(r => print(" ", r._id, r.revenue, "units:", r.units));

const [dash] = db.orders.aggregate([
  { $facet: {
      byStatus: [{ $group: { _id: "$status", n: { $sum: 1 } } }, { $sort: { _id: 1 } }],
      topSku: [{ $match: { status: "paid" } }, { $unwind: "$lines" },
               { $group: { _id: "$lines.sku", units: { $sum: "$lines.qty" } } }, { $sort: { units: -1 } }, { $limit: 1 }],
  } },
]).toArray();
print("orders by status:", JSON.stringify(dash.byStatus));
print("top product:", JSON.stringify(dash.topSku));

Output

revenue by category:
  kitchen 75 units: 7
  food 36 units: 3
orders by status: [{"_id":"paid","n":3},{"_id":"refunded","n":1}]
top product: [{"_id":"mug","units":6}]

Run this on your computer — see “Get Started” for setup.

Common mistakes

  • $match after $unwind or $group when it could filter first, processing far more documents than needed.
  • Running heavy analytics pipelines on the primary during peak traffic; use secondaries or analytics nodes for reporting.
  • Large $group stages exceeding memory limits; filter first, or allow disk use for batch jobs.

Key points

  • Pipelines transform documents stage by stage.
  • Filter early with $match so indexes and smaller inputs help.
  • $facet computes several summaries in one query.

Further reading: Aggregation pipeline · Aggregation pipeline optimization

Practice questions

Why put $match as early as possible in a pipeline?

Answer

Early filtering can use indexes and reduces the documents every later stage has to process.

What does $unwind do?

Answer

It outputs one document per element of an array field, so array elements can be grouped or filtered individually.

What is $facet useful for?

Answer

Running several sub-pipelines over the same input in one query, such as counts and top-N lists for a dashboard.

Open in the app to mark lessons complete, take notes and track your progress.

Was this page helpful?