Skip to content

Advanced Aggregation

$lookup with a Pipeline

Join with extra conditions, projections and limits inside the lookup itself.

AdvancedAbout 8 minutes

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"
    }
  }
])
Only non-cancelled orders, and only the fields the report needs

$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"
  }
}
Same join, 5.0 syntax — equality plus a filter

Interview question

When do you use a pipeline inside $lookup instead of localField / foreignField?

Think about it first.