querydrill

Learn ›Advanced aggregation ›Joining collections with $lookup

Pipeline $lookup with let and $expr

$and$eq$expr$lookup$match

The basic $lookup can only match one field against another. The pipeline form runs a whole sub-pipeline against the foreign collection, which means you can filter during the join - and it is the form interviewers ask about.

Suppose:

users
orders

Question:

For each user, find only their completed orders.

You can use a pipeline inside $lookup.

{
  $lookup: {
    from: "orders",

    let: {
      userId: "$_id"
    },

    pipeline: [
      {
        $match: {
          $expr: {
            $and: [
              {
                $eq: [
                  "$userId",
                  "$$userId"
                ]
              },
              {
                $eq: [
                  "$status",
                  "completed"
                ]
              }
            ]
          }
        }
      }
    ],

    as: "orders"
  }
}

This looks scary, but break it down.

let

let: {
  userId: "$_id"
}

For the current user:

Current document:

{
  _id: 101,
  name: "Denish"
}

We create:

$$userId = 101

$expr

Normally:

{
  userId: 101
}

compares a field against a fixed value.

But here we want:

orders.userId
=
current user's _id

So we need expressions:

$expr: {
  $eq: [
    "$userId",
    "$$userId"
  ]
}

Important distinction:

$field
→ field from current pipeline document

$$variable
→ aggregation variable

This $lookup pattern is worth knowing.

Try it

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

Only completed orders came back. The basic $lookup would have joined all of them and left you to filter afterwards - same answer, more documents carried through the pipeline to get it.

Practise this

1 exercise