What you will build
An aggregation pipeline passes documents through stages. Here, each stage has one job: keep paid orders, calculate totals per customer, then sort the totals.
Use a disposable database and the demo_orders collection. The following mongosh example assumes that collection is empty.
db.demo_orders.insertMany([
{customer: "Ada", status: "paid", total_cents: 2500},
{customer: "Ada", status: "paid", total_cents: 1500},
{customer: "Lin", status: "paid", total_cents: 3000},
{customer: "Lin", status: "pending", total_cents: 9000}
]);
db.demo_orders.aggregate([
{$match: {status: "paid"}},
{$group: {
_id: "$customer",
revenue_cents: {$sum: "$total_cents"},
order_count: {$sum: 1}
}},
{$sort: {revenue_cents: -1, _id: 1}}
]);
The expected result has Ada with 4000 cents across two paid orders, followed by Lin with 3000 cents across one paid order. The pending order does not contribute.
Read the stages
The match stage narrows the input. The group stage changes the result's shape: the customer becomes the group identifier, and accumulators produce totals. The sort stage orders those new grouped documents.
A field available before grouping is not automatically available afterward. Include the accumulator or grouping expression you need.
Watch the units and data types
Using integer cents avoids a floating-point representation issue in this small example. Real financial systems need a documented monetary representation, currency handling, and a rounding policy.
Validate missing or malformed amounts before relying on a report.
Grow the query deliberately
Try adding a customer filter to the match stage. Then inspect the execution plan on a larger development dataset to understand whether an index reduces the input work.
Running the insertion twice doubles the sample orders. Start from a fresh exercise collection when checking the expected totals.