querydrill

Learn ›Advanced aggregation ›Array and conditional expressions

$filter, and how it differs from $match

arrays$eq$filter$match$project

$filter narrows an array in place. The document stays one document - which is exactly what makes it different from $match, which drops whole documents, and $unwind, which multiplies them.

Suppose:

{
  userId: 101,

  orders: [
    {
      product: "Laptop",
      status: "completed"
    },
    {
      product: "Mouse",
      status: "pending"
    }
  ]
}

You want to keep only completed orders.

{
  $project: {
    userId: 1,

    completedOrders: {
      $filter: {
        input: "$orders",
        as: "order",

        cond: {
          $eq: [
            "$$order.status",
            "completed"
          ]
        }
      }
    }
  }
}

Result:

{
  userId: 101,

  completedOrders: [
    {
      product: "Laptop",
      status: "completed"
    }
  ]
}

Mental model:

$filter

Take array
    ↓
Check each element
    ↓
Keep elements matching condition

$filter vs $match

Very important.

$match

Filters DOCUMENTS.

Document A → keep/remove
Document B → keep/remove
$filter

Filters ARRAY ELEMENTS inside a document.

items[]
   ↓
keep/remove individual items

Example:

Need completed orders only?

If each document is an order:

$match

If orders are inside:

user.orders[]

and you want to keep the user but filter their orders:

$filter

This distinction is extremely useful.

Try it

Each user in the sample data carries an embedded orders array:

db.users.aggregate([
  {
    $project: {
      _id: 0,
      name: 1,
      completedOrders: {
        $filter: {
          input: "$orders",
          as: "order",
          cond: { $eq: ["$$order.status", "completed"] }
        }
      }
    }
  },
  { $limit: 4 }
])

Four users in, four users out - some with an empty array. That is the difference: $match would have removed those rows entirely.

Practise this

1 exercise