Aggregation: $lookup and Advanced Stages
Introduction
When data lives in separate collections, find alone cannot join them—you either query twice in application code or use $lookup in the aggregation pipeline. This chapter joins users, posts, and comments, then introduces $facet, $bucket, $merge, and $out for multi-report and materialized views.
Prerequisites
Seed Referenced Collections
$lookup — Left Outer Join
Join posts with users on authorId:
Code explanation:
from— other collection name (same database)as— array of matched docs (0 or 1 for FK-style)$unwind— flattenauthorarray to object fields
SQL analogy: LEFT JOIN users ON posts.author_id = users._id.
Nested $lookup — Posts with Comments
For large comment threads, paginate comments in a separate query instead of loading all in one aggregation.
$lookup with Pipeline (Correlated Subquery)
MongoDB 3.6+ pipeline form filters joined collection:
Implements subset pattern — Common Modeling Patterns.
$facet — Multiple Pipelines in One Pass
Dashboard stats without three round trips:
Result shape:
{
byCustomer: [ ... ],
overall: [ { revenue: 80, count: 2 } ],
byDay: [ ... ]
}$bucket and $bucketAuto
Histogram-style grouping:
$bucketAuto lets MongoDB choose boundaries from sample size.
$out and $merge — Write Results
Materialize aggregation output to a collection:
// Replace collection contents (permissions required)
db.orders.aggregate([
{ $match: { status: "paid" } },
{
$group: {
_id: "$customer",
revenue: { $sum: { $toDouble: "$amount" } }
}
},
{ $out: "customer_revenue" }
])$merge upserts into existing collection (MongoDB 4.2+):
Use for nightly rollup jobs feeding dashboards — Event analytics project.
$replaceRoot and $addFields
Promote nested document to root:
$addFields adds computed fields without dropping others (alias $set stage).
Performance Notes
| Tip | Reason |
|---|---|
$match first | Less data in pipeline |
Index foreignField | Faster $lookup |
Limit before $lookup | Fewer join rows |
Avoid $lookup on huge collections without filter | Memory and time |
Post-Chapter Checklist
-
$lookupjoined posts → users - Pipeline
$lookupwith$limiton comments -
$facetreturned multiple report sections - You know
$outvs$mergeuse cases
FAQ
$lookup vs SQL JOIN?
Similar left outer join. MongoDB has no inner join stage—filter with $match after lookup.
Cross-database $lookup?
Not in same aggregation—use $lookup only within one database; federated queries need app layer or $unionWith (same DB).
$graphLookup?
Recursive join for trees (categories, org chart)—advanced; not covered here.
Sharded $lookup?
Both collections must shard on compatible keys or performance suffers — chapter 15.
Still use two queries?
For simple post + author, two findOne calls may be clearer than aggregation—profile both.