Goal: Combine data from two separate collections. Key Idea: MongoDB's equivalent of
LEFT OUTER JOINis the$lookupstage inside an Aggregation Pipeline.
| SQL JOIN | MongoDB $lookup |
|
|---|---|---|
| Shape | Flat rows — data is "spread out" | Hierarchical — joined data is an array inside the document |
| 1 order with 3 products | Returns 3 rows | Returns 1 document with an array of 3 products |
SELECT *
FROM Orders
LEFT JOIN Products ON Orders.product_id = Products.id;db.orders.aggregate([
{
$lookup: {
from: "products", // The foreign collection to join
localField: "product_id", // Field in the Orders collection
foreignField: "_id", // Field in the Products collection
as: "product_details" // Name of the new output array field
}
}
])$lookup: {
from: "products", // Which collection to pull from?
localField: "product_id", // Which field in THIS collection?
foreignField: "_id", // Which field in the OTHER collection?
as: "product_details" // Store results in this new field
}{
"_id": "order_1",
"customer": "Alice",
"product_id": "prod_42",
"product_details": [
{
"_id": "prod_42",
"name": "Keyboard",
"price": 79.99
}
]
}Notice:
product_detailsis an array, even if there's only one match.
Use $unwind after $lookup to flatten the array into a single object:
db.orders.aggregate([
{
$lookup: {
from: "products",
localField: "product_id",
foreignField: "_id",
as: "product_details"
}
},
{
$unwind: "$product_details" // Flatten the array
}
])$lookupis part of the Aggregation Pipeline — always insideaggregate([...]).- If you find yourself using
$lookupfor every query, you may be better off embedding the data instead (see Rules of Thumb). $lookupis more expensive than a simplefind()— use it intentionally.