Skip to content

$lookup & $unwind

$lookup joins documents from another collection. $unwind flattens arrays.

{
$lookup: {
from: "users",
localField: "userId",
foreignField: "_id",
as: "user"
}
}
db.orders.aggregate([
{
$lookup: {
from: "users",
localField: "userId",
foreignField: "_id",
as: "user"
}
}
])

The result contains user as an array:

{
_id: ObjectId("..."),
userId: ObjectId("..."),
totalAmount: 2500,
user: [
{ _id: ObjectId("..."), name: "Alice" }
]
}

$unwind converts each array element into a separate document.

db.orders.aggregate([
{
$lookup: {
from: "users",
localField: "userId",
foreignField: "_id",
as: "user"
}
},
{ $unwind: "$user" }
])

Use this when a missing match should not remove the document.

{ $unwind: { path: "$user", preserveNullAndEmptyArrays: true } }

Use pipeline form when the join needs filters, projections, or multiple conditions.

db.users.aggregate([
{
$lookup: {
from: "orders",
let: { userId: "$_id" },
pipeline: [
{ $match: { $expr: { $eq: ["$userId", "$$userId"] } } },
{ $match: { status: "completed" } },
{ $project: { totalAmount: 1, createdAt: 1 } }
],
as: "completedOrders"
}
}
])

Flatten order items:

db.orders.aggregate([
{ $unwind: "$items" },
{
$group: {
_id: "$items.productId",
totalSold: { $sum: "$items.quantity" }
}
}
])