MongoDB Aggregation Pipeline

The MongoDB aggregation pipeline processes documents through a series of stages, each transforming the data and passing it on. It is MongoDB's answer to SQL GROUP BY, JOIN and calculated columns, and is used for reports, dashboards and analytics.

$match and $group

$match filters like find(). $group groups by _id and calculates accumulators such as $sum, $avg, $min, $max and $count.

db.products.aggregate([
  { $match: { stock: { $gt: 0 } } },
  { $group: {
      _id: "$category",
      products: { $sum: 1 },
      totalStock: { $sum: "$stock" }
  } },
  { $sort: { totalStock: -1 } }
])
[
  { _id: 'Snacks', products: 2, totalStock: 560 },
  { _id: 'Beverages', products: 2, totalStock: 380 },
  { _id: 'Grains', products: 1, totalStock: 120 },
  { _id: 'Oils', products: 1, totalStock: 45 }
]

$project: Shaping Output

$project includes, excludes or calculates fields. Here we compute the stock value of each product.

db.products.aggregate([
  { $project: { _id: 0, name: 1, value: { $multiply: ["$price", "$stock"] } } },
  { $sort: { value: -1 } },
  { $limit: 2 }
])
[
  { name: 'Dark Chocolate', value: 2125 },
  { name: 'Green Tea', value: 1800 }
]

$unwind

$unwind outputs one document per array element, so you can group by array contents.

db.orders.aggregate([
  { $unwind: "$items" },
  { $group: { _id: "$items.product", units: { $sum: "$items.qty" } } }
])
[ { _id: 'Green Tea', units: 25 } ]

$lookup: Joining Collections

$lookup brings matching documents from another collection into an array field, like a LEFT JOIN.

db.orders.aggregate([
  { $match: { orderNo: 101 } },
  { $lookup: {
      from: "customers",
      localField: "customerId",
      foreignField: "_id",
      as: "customer"
  } },
  { $project: { _id: 0, orderNo: 1, total: 1, "customer.name": 1 } }
])
[ { orderNo: 101, total: 125, customer: [ { name: 'Alpha Traders' } ] } ]

Pipeline Tips

  • Put $match and $sort early so indexes can be used
  • Use $limit early when possible to reduce work
  • Stages can repeat: $group, then $match on the result (like HAVING)
  • Write results to a collection with $out or $merge

Frequently asked questions

What is the difference between find() and aggregate()?

find() filters and projects documents. aggregate() can also group, reshape, join and calculate across documents through multiple stages.

How do I do GROUP BY in MongoDB?

Use a $group stage with _id set to the field to group on, for example { $group: { _id: "$category", n: { $sum: 1 } } }.

Is $lookup slow?

It can be on large collections. Make sure the foreignField is indexed, filter early with $match, and consider embedding if you join the same data constantly.