MongoDB Aggregation

What Is MongoDB Aggregation?
MongoDB Aggregation is a framework for processing documents through a sequence of stages to filter, transform, group, calculate, and analyze data.
Use it when a normal query is not enough to produce the result you need.
Documents
↓
Aggregation Pipeline
↓
Filter
↓
Transform
↓
Group
↓
Sort
↓
Results
Aggregation = overall data-processing framework
Pipeline = ordered sequence of stages
Stage = one processing step Why Use Aggregation?
Aggregation lets you combine several processing steps in one pipeline: filter, transform, calculate, group, sort, work with arrays, combine related collections, reshape data, and build reports or analytics.
db.sales.aggregate([
{
$match: {
status: "completed"
}
}
]) How It Works
aggregate()starts a pipeline on thesalescollection.$matchkeeps only documents withstatus: "completed".- Those filtered documents become the pipeline result.
Aggregation Pipeline
An aggregation pipeline is an ordered sequence of stages. Each stage receives documents, processes them, and passes its output to the next stage.
db.sales.aggregate([
{
$match: {
status: "completed"
}
},
{
$group: {
_id: "$category",
totalSales: {
$sum: "$amount"
}
}
},
{
$sort: {
totalSales: -1
}
}
]) Documents
↓
$match
↓
$group
↓
$sort
↓
Results How It Works
Pipeline Stages
Each stage performs one specific operation. This table introduces common stages—not every stage MongoDB supports.
| Stage | Purpose |
|---|---|
$match | Filters documents |
$project | Selects or reshapes fields |
$set | Adds or modifies fields |
$addFields | Adds or modifies fields |
$group | Groups documents |
$sort | Sorts documents |
$limit | Limits results |
$skip | Skips documents |
$unwind | Expands array elements |
$lookup | Combines related collections |
$count | Counts documents |
$facet | Runs multiple sub-pipelines |
Aggregation Stage vs Expression
Keep these concepts separate: a stage is a pipeline step; an expression calculates or produces a value inside a stage.
Stage
{
$match: {
status: "completed"
}
} Expression
{
$project: {
total: {
$add: ["$price", "$tax"]
}
}
} Pipeline
|
+-- Stage
| |
| +-- may contain expressions
|
+-- Stage
|
+-- may contain expressions $match is the stage. $add is an expression used inside $project. Browse more expressions in MongoDB Operators.
Where Do Accumulators Fit?
Accumulators calculate values from multiple input values or documents in aggregation contexts. They are not the same topic as Aggregation itself.
{
$group: {
_id: "$category",
total: {
$sum: "$amount"
},
average: {
$avg: "$amount"
}
}
} Aggregation
↓
Pipeline
↓
$group Stage
↓
Accumulators
┌───────────────┐
│ $sum │
│ $avg │
└───────────────┘ $groupis an aggregation stage.$sumand$avgare accumulators in this example.- The pipeline contains the stage; the accumulators calculate values within that stage.
Pseudocode
Read documents
↓
Run Stage 1
↓
Pass results to Stage 2
↓
Run Stage 2
↓
Pass results to Stage 3
↓
Return final results FOR each pipeline stage
process the current documents
pass the result to the next stage
RETURN final documents $match
$match filters documents so only matching ones continue through the pipeline.
db.orders.aggregate([
{
$match: {
status: "completed"
}
}
]) How It Works
- MongoDB examines each input document.
- Documents with
status: "completed"pass through. - Other documents leave the pipeline stream.
$project
$project selects or reshapes fields.
db.products.aggregate([
{
$project: {
name: 1,
price: 1
}
}
]) How It Works
- MongoDB processes each document.
- Only the requested fields are included in the output (plus
_idunless excluded). - The pipeline continues with the reshaped documents.
$set
$set adds or modifies fields. Expressions inside it calculate new values.
db.products.aggregate([
{
$set: {
discountedPrice: {
$multiply: ["$price", 0.9]
}
}
}
]) How It Works
$setis the stage that adds or updates a field.$multiplyis the expression that computesprice × 0.9.- The new
discountedPricefield is passed to later stages.
$group
$group combines documents that share a grouping key. Accumulators can calculate values for each group.
db.sales.aggregate([
{
$group: {
_id: "$category",
totalSales: {
$sum: "$amount"
}
}
}
]) How It Works
- Documents are grouped by
category(_idis the grouping key). - Each category becomes one group.
$sumaddsamountvalues inside that group (accumulator, not a stage).- One output document is produced per group.
For accumulator operators and patterns, see MongoDB Accumulators.
$sort
$sort orders documents. Use 1 for ascending and -1 for descending.
db.products.aggregate([
{
$sort: {
price: -1
}
}
]) How It Works
- Documents reaching
$sortare ordered byprice. -1puts higher prices first.- Sorted documents continue to the next stage.
$limit and $skip
$limit keeps only the first N documents that reach it. $skip discards the first N documents—useful for simple pagination patterns.
db.products.aggregate([
{
$sort: {
price: -1
}
},
{
$limit: 5
}
]) How It Works
$sortorders products by price (highest first).$limit: 5keeps only the first five sorted documents.
db.products.aggregate([
{
$skip: 10
},
{
$limit: 10
}
]) How It Works
$skip: 10drops the first 10 documents.$limit: 10keeps the next 10—a basic page-style window.- For very large collections, other pagination strategies may scale better than large skips.
$unwind
$unwind expands an array field so each element becomes its own pipeline document.
{
name: "Laptop",
tags: ["computer", "electronics"]
} {
$unwind: "$tags"
} One document
↓
$unwind
↓
Multiple documents
{ name: "Laptop", tags: "computer" }
{ name: "Laptop", tags: "electronics" } How It Works
- MongoDB reads the
tagsarray. - Each array element becomes a separate document in the stream.
- Other fields (like
name) are copied onto each output document.
$lookup
$lookup performs a left outer join with another collection in the same database. Matching documents are added as an array field.
orders
|
v
$lookup
|
v
customers
|
v
combined result
(joined docs in an array field) db.orders.aggregate([
{
$lookup: {
from: "customers",
localField: "customerId",
foreignField: "_id",
as: "customer"
}
}
]) How It Works
- For each order, MongoDB looks up matching customers.
- Matches are placed in the
customerarray field. - Orders without matches still continue, with an empty array.
Worked Walkthrough — Sales by Category
Group sales by category, total the amounts, then sort highest first.
db.sales.insertMany([
{ product: "Laptop", category: "Electronics", amount: 1000 },
{ product: "Mouse", category: "Electronics", amount: 50 },
{ product: "Chair", category: "Furniture", amount: 200 },
{ product: "Desk", category: "Furniture", amount: 400 }
]) db.sales.aggregate([
{
$group: {
_id: "$category",
totalSales: {
$sum: "$amount"
}
}
},
{
$sort: {
totalSales: -1
}
}
]) How It Works
- MongoDB reads the sales documents.
$groupcreates a group for eachcategory.$sumaddsamountvalues inside each group (accumulator in the aggregation pipeline).$sortorders groups bytotalSalesdescending.- MongoDB returns the final grouped documents.
Multiple Stages Together
A typical report pipeline filters first, then groups, sorts, and limits.
db.sales.aggregate([
{
$match: {
status: "completed"
}
},
{
$group: {
_id: "$category",
totalSales: {
$sum: "$amount"
}
}
},
{
$sort: {
totalSales: -1
}
},
{
$limit: 5
}
]) All Sales
↓
$match
↓
Completed Sales
↓
$group
↓
Category Totals
↓
$sort
↓
Highest First
↓
$limit
↓
Top 5 How It Works
$matchkeeps completed sales only.$grouptotals amount per category.$sortputs the largest totals first.$limitreturns only the top 5 groups.
Aggregation vs find()
Aggregation does not replace find(). Use the simplest operation that solves the problem.
find() | aggregate() |
|---|---|
| Retrieves matching documents | Processes documents through stages |
| Best for straightforward queries | Useful for complex transforms and analysis |
| Primarily filters documents | Can filter, transform, group, calculate, sort, join, reshape |
| Returns matching documents | Returns the result of the pipeline |
Pipeline Order & Filtering Early
Stage order matters. Changing order can change the result, how many documents pass between stages, and how much work the pipeline does.
db.sales.aggregate([
{ $match: { status: "completed" } },
{ $group: { _id: "$category", totalSales: { $sum: "$amount" } } },
{ $sort: { totalSales: -1 } }
])
$match → $group → $sort Filtering early often reduces later work—but $match is not required to be first in every pipeline, and MongoDB's optimizer can reorder some operations when it is safe to do so.
Many Documents
↓
$match
↓
Fewer Documents
↓
$group / $sort / ... Arrays, joins, and output
- Arrays: inspect array fields with expressions, or use
$unwindto turn elements into separate documents. - Multiple collections:
$lookupcombines related data; joined matches usually arrive as an array field. - Output: most pipelines return a result cursor.
$outand$mergecan write results to a collection.
Edge Cases & Pitfalls
Common mistakes when learning MongoDB aggregation.
Stage order can affect results
Sorting before grouping is not the same as grouping before sorting. Design the flow intentionally.
$group changes document shape
Output focuses on _id plus accumulator fields. Original fields are not kept unless you include them in the group expression.
_id is the grouping key
Whatever you put in _id defines the buckets. Wrong key → wrong groups.
$project can drop fields
Later stages only see fields you kept or computed. Project carefully before grouping or sorting.
$unwind multiplies documents
An array of length 5 can turn 1 document into 5. Plan for larger intermediate streams.
$lookup returns an array
Joined matches land in the as field as an array—even when there is zero or one match.
Missing and null values matter
Expressions and grouping treat missing or null fields differently from numbers or strings. Check your data types.
Do not confuse the three layers
Aggregation ≠ accumulators. Stages ≠ expressions. Accumulators are not pipeline stages.
Aggregation is not automatically faster
A pipeline can do more work than find(). Prefer the simplest tool that meets the need.
Performance Notes
| Idea | Practical tip |
|---|---|
| Filter early | Reduce unnecessary documents before heavy stages when it fits the result you need |
| Indexes | Suitable $match / $sort patterns can use indexes |
$group / $sort | Large groups and sorts can be memory- and CPU-heavy |
$lookup | Cost depends on data size, keys, and how many matches are found |
$unwind | Can multiply document count in the pipeline |
| Explain | Use explain / query analysis for important pipelines |
| Context | Results depend on data size, indexes, pipeline shape, workload, and MongoDB version |
Avoid absolute claims. Filtering can reduce work, but it does not guarantee a fast pipeline by itself.
Cheat Sheet
| Concept | Remember |
|---|---|
| Aggregation | Processes documents through a pipeline |
| Pipeline | Ordered sequence of stages |
| Stage | Performs one processing step |
| Expression | Produces or calculates a value |
$match | Filters documents |
$project | Shapes documents |
$set | Adds or modifies fields |
$group | Groups documents |
$sort | Sorts documents |
$limit | Limits results |
$skip | Skips documents |
$unwind | Expands arrays |
$lookup | Combines related collections |
| Accumulator | Calculates values in supported aggregation contexts — see Accumulators |
Key Takeaways
- MongoDB Aggregation processes documents through a pipeline.
- A pipeline contains ordered stages; each stage does one job.
$matchfilters;$project/$setreshape;$groupcombines.- Accumulators such as
$sumcalculate values in aggregation contexts—they are not the Aggregation topic itself. $unwindworks with arrays;$lookupcombines related collections.- Pipeline design affects both results and performance.
- Aggregation and accumulators are related but not the same concept.
One line: Aggregation runs documents through ordered stages; stages may use expressions; $group may use accumulators—keep those layers distinct.
Frequently Asked Questions
Next: Replication
Keep multiple live copies of your data with replica sets.
14 people found this page helpful
