Evaluation Query Operators ($regex, $expr, $mod)
Evaluation Query Operators ($regex, $expr, $mod)
Level 4 — Advanced Querying The BSON query operators used to perform calculations or logic checks during query execution, specifically string pattern matching (
$regex), comparing fields within the same document ($expr), and modulo math ($mod).
1. Prerequisites
- Query Filter (Filter Document) — The parent query filters context.
find()/findOne()— Evaluating complex query expression operators.
2. Term Category
Query Operator (Custom Evaluation Operators): Evaluation Operators (jsonSchema, regex, where) evaluate fields against complex expressions or custom logic.
3. Explanation
Environment Context
- MongoDB Core (Evaluated in memory. Aggregation expressions inside
$exprrequire parsing by the document projection engine, making these operations CPU-intensive compared to simple indexes lookups).
(1) Design Motivation — "Why did we design this?"
Standard query filters evaluate a field against a static, constant value:
db.products.find({ price: { $gt: 50 } })
However, some queries require dynamic logic:
- Regex Search: Finding users whose names start with
"Al"or contain"smith". - Cross-Field Comparison: Finding orders where the
amount_paidis less than thetotal_billinside the same document. - Arithmetic Modulo: Distributing tasks across workers by matching documents where the
idis divisible by 4.
In PostgreSQL, cross-field checks are simple:
SELECT * FROM orders WHERE amount_paid < total_bill;
We designed the Evaluation Operators in MongoDB to support these calculations.
Because MongoDB's standard filter syntax is strictly key-value based, we use operators like $expr to unlock aggregation expression syntax inside query filters, allowing you to run cross-field math.
(2) The Three Evaluation Operators
1. $regex (Regular Expression)
Performs string pattern matching queries. (We will explore $regex in Term #57).
2. $expr (Aggregation Expressions)
Enables comparing fields within the same document or executing aggregation functions in the query filter.
- Syntax:
{ $expr: { $lt: [ "$amount_paid", "$total_bill" ] } } - Field Prefixes: Inside
$expr, you must prefix field names with$(e.g."$amount_paid") to tell the compiler to evaluate the field's value rather than treating the string as a literal text label.
3. $mod (Modulo Arithmetic)
Finds documents where a numeric field modulo a divisor equals a remainder.
- Syntax:
{ field: { $mod: [ divisor, remainder ] } } - Example:
{ qty: { $mod: [ 2, 0 ] } }(Finds even quantities).
(3) Reality Metaphor (The Clerk's Calculator)
- Standard Query: A warehouse checker looks at a box label and checks: "Is the price tag > 50?" (Fast, simple check).
$exprQuery: The checker pulls out a Scientific Calculator.- They read the value in Box A, read the value in Box B, subtract them, and check if the difference is positive.
- This takes more mental effort (CPU cycles) and slows down the inspection line, but lets them perform complex logic.
(4) Code Examples
1. Comparing Two Fields in a Document ($expr)
Find all orders where users paid less than their total balance:
db.orders.insertOne({
_id: 1,
total_bill: NumberDecimal("100.00"),
amount_paid: NumberDecimal("80.00")
});
db.orders.find({
$expr: { $lt: [ "$amount_paid", "$total_bill" ] } // Uses aggregation math
});
2. Filtering Even Numbers ($mod)
Select items that fit exactly into groups of 3:
db.products.find({
stock: { $mod: [ 3, 0 ] } // Matches stock 3, 6, 9, 12, etc.
});
4. Common Mistakes & Pitfalls
Mistake 1: Omitting the 'expr' operator array
The mistake: Writing { $expr: { $lt: [ "amount_paid", "total_bill" ] } } to compare the two fields.
Why it's wrong: Without the $ prefix, MongoDB treats "amount_paid" and "total_bill" as literal text strings.
It compares the spelling of the string words alphabetically.
Since "amount_paid" is alphabetically less than "total_bill", this query will evaluate to true for every document in the collection, returning incorrect data.
Fix: Always prefix the field keys with $ inside aggregation expressions to instruct the compiler to read the field value.
// CORRECT
{ $expr: { $lt: [ "$amount_paid", "$total_bill" ] } }
Mistake 2: Using $where or $expr Expressions That Prevent Index Usage on Large Collections
The mistake: Running db.orders.find({ $where: "this.price > this.cost" }) on 10M documents.
Why it's wrong: $where executes JavaScript evaluation code per document in a single-threaded engine, bypassing B-Tree indexes and causing severe CPU degradation. Use $expr with indexes where possible.
Incorrect:
db.orders.find({ $where: "this.price > this.cost" }); // ❌ Slow single-threaded JS evaluation!
Fix:
db.orders.find({ $expr: { $gt: ["$price", "$cost"] } }); // Native BSON expression evaluation
Mistake 3: Confusing Document Field Paths $field inside $expr with Plain Field Names
The mistake: Writing db.orders.find({ $expr: { $gt: ["price", "cost"] } }) without $ prefixes.
Why it's wrong: Inside $expr aggregation expressions, field paths MUST be prefixed with $ (e.g. "$price", "$cost"). Un-prefixed "price" is parsed as a literal string.
Incorrect:
db.orders.find({ $expr: { $gt: ["price", "cost"] } }); // ❌ Compares literal strings "price" and "cost"!
Fix:
db.orders.find({ $expr: { $gt: ["$price", "$cost"] } }); // Compares document field values
5. Practice Exercises
Exercise 1: Field-to-Field Comparisons with $expr
Scenario:
Query collection orders for documents where amountPaid is greater than totalAmount.
Requirements:
- Use
$expr: { $gt: ["$amountPaid", "$totalAmount"] }.
Answer
Implementation
db.orders.find({
$expr: { $gt: ["$amountPaid", "$totalAmount"] }
});
Technical Explanation
$exprenables aggregation expressions and field-to-field comparisons within standardfind()query filters.- Field names prefixed with
$(e.g."$amountPaid") reference document values dynamically. - Solves queries that compare two fields on the same document.
Exercise 2: Modulo Arithmetic Matching with $mod
Scenario:
Query collection inventory for documents where quantity is an even number (quantity % 2 == 0).
Requirements:
- Use
{ quantity: { $mod: [2, 0] } }.
Answer
Exercise 3: Pattern Matching with $regex
Scenario:
Find all users whose email ends with "@example.com".
Requirements:
- Use
{ email: { $regex: "@example\.com$" } }.
Answer
6. Related Terms
$regex(Regular Expressions) — String matching.
7. Key Takeaways
- Evaluation operators execute calculations or logic checks during queries.
$exprallows the comparison of two fields within the same document.- Fields inside
$exprmust be prefixed with$to read their values. $modperforms modulo calculations on numbers (e.g. finding even numbers).- Aggregation functions and
$exprbypass index optimization; use them carefully. - Limit evaluation queries on large datasets to prevent collection scans.