$expr

Definition

New in version 3.6.

$expr has the following syntax:

  1. { $expr: { <expression> } }

The arguments can be any valid aggregation expression. For more information, seeExpressions.

Behavior

$expr can build query expressions that compare fieldsfrom the same document in a $match stage.

If the $match stage is part of a $lookup stage,$expr can compare fields using let variables. SeeSpecify Multiple Join Conditions with $lookup for an example.

$expr does not support multikey indexes.

Examples

Compare Two Fields from A Single Document

Consider an monthlyBudget collection with the following documents:

  1. { "_id" : 1, "category" : "food", "budget": 400, "spent": 450 }
  2. { "_id" : 2, "category" : "drinks", "budget": 100, "spent": 150 }
  3. { "_id" : 3, "category" : "clothes", "budget": 100, "spent": 50 }
  4. { "_id" : 4, "category" : "misc", "budget": 500, "spent": 300 }
  5. { "_id" : 5, "category" : "travel", "budget": 200, "spent": 650 }

The following operation uses $expr to find documentswhere the spent amount exceeds the budget:

  1. db.monthlyBudget.find( { $expr: { $gt: [ "$spent" , "$budget" ] } } )

The operation returns the following results:

  1. { "_id" : 1, "category" : "food", "budget" : 400, "spent" : 450 }
  2. { "_id" : 2, "category" : "drinks", "budget" : 100, "spent" : 150 }
  3. { "_id" : 5, "category" : "travel", "budget" : 200, "spent" : 650 }

Using $expr With Conditional Statements

Some queries require the ability to execute conditional logic whendefining a query filter. The aggregation framework provides the$cond operator to express conditional statements. By using$expr with the $cond operator, you canspecify a conditional filter for your query statement.

Create a sample supplies collection with the following documents:

  1. db.supplies.insertMany([
  2. { "_id" : 1, "item" : "binder", "qty" : NumberInt("100"), "price" : NumberDecimal("12") },
  3. { "_id" : 2, "item" : "notebook", "qty" : NumberInt("200"), "price" : NumberDecimal("8") },
  4. { "_id" : 3, "item" : "pencil", "qty" : NumberInt("50"), "price" : NumberDecimal("6") },
  5. { "_id" : 4, "item" : "eraser", "qty" : NumberInt("150"), "price" : NumberDecimal("3") },
  6. { "_id" : 5, "item" : "legal pad", "qty" : NumberInt("42"), "price" : NumberDecimal("10") }
  7. ])

Assume that for an upcoming sale next month, you want to discount theprices such that:

  • If qty is greater than or equal to 100, the discounted price willbe 0.5 of the price.
  • If qty is less than 100, the discounted price is 0.75 of theprice.

Before applying the discounts, you would like to know which items in thesupplies collection have a discounted price of less than 5.

The following example uses $expr with $cond tocalculate the discounted price based on the qty and$lt to return documents whose calculated discount priceis less than NumberDecimal("5"):

  1. // Aggregation expression to calculate discounted price
  2.  
  3. let discountedPrice = {
  4. $cond: {
  5. if: { $gte: ["$qty", 100] },
  6. then: { $multiply: ["$price", NumberDecimal("0.50")] },
  7. else: { $multiply: ["$price", NumberDecimal("0.75")] }
  8. }
  9. };
  10.  
  11. // Query the supplies collection using the aggregation expression
  12.  
  13. db.supplies.find( { $expr: { $lt:[ discountedPrice, NumberDecimal("5") ] } });

The following table shows the discounted price for each document andwhether discounted price is less than NumberDecimal("5") (i.e.whether the document meets the query condition).

DocumentDiscounted Price< NumberDecimal(“5”)
{“_id”: 1, “item”: “binder”, “qty”: 100, “price”: NumberDecimal(“12”) }NumberDecimal(“6.00”)false
{“_id”: 2, “item”: “noteboook”, “qty”: 200, “price”: NumberDecimal(“8”) }NumberDecimal(“4.00”)true
{“_id”: 3, “item”: “pencil”, “qty”: 50, “price”: NumberDecimal(“6”) }NumberDecimal(“4.50”)true
{“_id”: 4, “item”: “eraser”, “qty”: 150, “price”: NumberDecimal(“3”) }NumberDecimal(“1.50”)true
{“_id”: 5, “item”: “legal pad”, “qty”: 42, “price”: NumberDecimal(“10”) }NumberDecimal(“7.50”)false

The db.collection.find() operation returns the documents whosecalculated discount price is less than NumberDecimal("5"):

  1. { "_id" : 2, "item" : "notebook", "qty": 200 , "price": NumberDecimal("8") }
  2. { "_id" : 3, "item" : "pencil", "qty": 50 , "price": NumberDecimal("6") }
  3. { "_id" : 4, "item" : "eraser", "qty": 150 , "price": NumberDecimal("3") }

Even though $cond calculates an effective discountedprice, that price is not reflected in the returned documents. Instead,the returned documents represent the matching documents in theiroriginal state. The find operation did not return the binderor legal pad documents, as their discounted price was greater than5.