mongodb 7.5.0MongoDB Aggregation Pipeline Examples
Build MongoDB aggregation examples that filter, reshape, group, sort, unwind, facet, and validate data, then inspect the real cursor in MongoG.
Think in pipeline stages
An aggregation pipeline passes documents through an ordered list of stages. Each stage can filter, reshape, group, join, or calculate values before passing its output to the next stage. The server performs the work and returns a cursor.
Prepare a small order dataset:
use("mongog_tutorial");
const orders = db.collection("aggregation_orders");
orders.deleteMany({ tutorial: "aggregation" });
orders.insertMany([
{ customer: "Ada", status: "paid", total: 120, placedAt: ISODate("2026-08-02T10:00:00Z"), tutorial: "aggregation" },
{ customer: "Ada", status: "paid", total: 80, placedAt: ISODate("2026-08-08T12:00:00Z"), tutorial: "aggregation" },
{ customer: "Grace", status: "paid", total: 210, placedAt: ISODate("2026-08-09T09:30:00Z"), tutorial: "aggregation" },
{ customer: "Grace", status: "cancelled", total: 45, placedAt: ISODate("2026-08-10T15:15:00Z"), tutorial: "aggregation" },
]);
Match, group, and sort
This pipeline keeps paid tutorial orders, groups them by customer, calculates totals, and orders the result:
orders.aggregate([
{ $match: { tutorial: "aggregation", status: "paid" } },
{
$group: {
_id: "$customer",
orderCount: { $sum: 1 },
revenue: { $sum: "$total" },
averageOrder: { $avg: "$total" },
},
},
{ $sort: { revenue: -1, _id: 1 } },
]);
Place selective $match stages early so fewer documents reach expensive stages. An index may support an early match and sort when the pipeline shape allows it.
Expected result
The cursor returns one summary for Grace and one for Ada. The cancelled order is excluded. MongoG pages the real aggregation cursor exactly as it does a find() cursor.
Reshape output with project and set
$project chooses or computes output fields. $set adds or replaces fields while retaining the rest of the document:
orders.aggregate([
{ $match: { tutorial: "aggregation", status: "paid" } },
{
$project: {
_id: 0,
customer: 1,
total: 1,
yearMonth: { $dateToString: { format: "%Y-%m", date: "$placedAt" } },
largeOrder: { $gte: ["$total", 150] },
},
},
{ $sort: { total: -1 } },
]);
Expressions inside stages refer to field values with a $ prefix. The string "$total" means the current document's total field; the number 150 is a literal.
Unwind arrays
$unwind emits one pipeline document for each element of an array. It is useful when grouping embedded line items:
db.collection("aggregation_carts").aggregate([
{ $match: { tutorial: "aggregation" } },
{ $unwind: "$items" },
{ $group: { _id: "$items.sku", units: { $sum: "$items.quantity" } } },
{ $sort: { units: -1 } },
]);
Decide how empty or missing arrays should behave. preserveNullAndEmptyArrays can keep them when the report requires it.
Watch for write stages
Most pipelines only read, but $out and $merge write their results. Treat them as write operations, review the target carefully, and use appropriate server permissions. MongoG's read-only mode attempts to block these stages, but database roles remain the real security boundary.
Join and branch pipeline work
$lookup adds related documents from another collection in the same database. $facet runs multiple sub-pipelines over the same input, which is useful for returning a page and summary counts together.
This facet calculates revenue bands and an overall summary after one selective match:
orders.aggregate([
{ $match: { tutorial: "aggregation", status: "paid" } },
{
$facet: {
byCustomer: [
{ $group: { _id: "$customer", revenue: { $sum: "$total" } } },
{ $sort: { revenue: -1, _id: 1 } },
],
totals: [
{
$group: {
_id: null,
orderCount: { $sum: 1 },
revenue: { $sum: "$total" },
},
},
{ $project: { _id: 0 } },
],
},
},
]);
Every facet receives the matched input, so multiple expensive branches can multiply work. Use only the outputs the request actually needs.
Handle null, missing, and conversion errors
Aggregation expressions can normalize inconsistent legacy values, but silent conversion can hide data-quality problems. Operators such as $ifNull, $convert, and $type make the policy explicit:
orders.aggregate([
{ $match: { tutorial: "aggregation" } },
{
$project: {
customer: { $ifNull: ["$customer", "Unknown"] },
normalizedTotal: {
$convert: {
input: "$total",
to: "decimal",
onError: null,
onNull: null,
},
},
},
},
]);
Measure how many conversions fail before building financial or operational reports on the normalized value.
Practice a reporting pipeline
Add items arrays to the tutorial orders. Build a pipeline that matches paid orders, unwinds line items, groups units and revenue by SKU, sorts by revenue, and uses a facet to return both the top products and a total. Explain where an index can help and where the server must process derived values.
Common mistakes
- Putting
$matchafter$groupwhen it could run first makes the server process more input than necessary. - Calling JavaScript functions inside a pipeline does not work like running local code; pipeline expressions execute on the server.
- Forgetting that
$unwindmultiplies documents can produce unexpectedly large intermediate results. - Using
$outor$mergewhile assuming every aggregation is read-only can modify data. - Adding many
$facetbranches can repeat expensive work and create a large result document. - Converting malformed legacy values to null without counting them can make a report appear complete when data was discarded.
Continue learning
Practice focused $group examples, evaluate pipeline access paths with indexes and explain plans, then learn $lookup relationship queries. Explore the official aggregation driver guide and pipeline stage reference.