Advanced Aggregation
$lookup with a Pipeline
Join with extra conditions, projections and limits inside the lookup itself.
The four-field $lookup is equality only. A pipeline inside $lookup filters, projects, and limits the joined collection before it is attached. You pass values from the parent with let; inside the pipeline they are $$ variables. $expr compares them to fields on the joined documents.
db.users.aggregate([
{ $match: { city: "Bangalore" } },
{
$lookup: {
from: "orders",
let: { uid: "$_id" },
pipeline: [
{ $match: { $expr: { $eq: ["$userId", "$$uid"] } } },
{ $match: { status: { $ne: "cancelled" } } },
{ $project: { _id: 0, amount: 1, status: 1 } }
],
as: "orders"
}
}
])$userId is a field on orders. $$uid is the variable from let. Mixing $ and $$ is the usual interview trap. You can $limit inside the pipeline — last three orders per user — without attaching the whole history. MongoDB 5+ also lets you keep localField / foreignField and add a pipeline for extra $match / $project; the equality is implied. let is the form interviews still write on a whiteboard.
{
$lookup: {
from: "orders",
localField: "_id",
foreignField: "userId",
pipeline: [
{ $match: { status: { $ne: "cancelled" } } }
],
as: "orders"
}
}Interview question
When do you use a pipeline inside $lookup instead of localField / foreignField?
When the join is not a single equality, or I need to filter, project, or limit the joined documents before they attach. let exposes parent fields as $$variables; $expr compares them to $fields on the foreign collection. Equality-only lookup cannot say 'orders that are not cancelled'.