MongoDB Evaluation Operators
Introduction
MongoDB evaluation operators allow you to perform sophisticated query operations beyond simple matching. These operators enable you to use regular expressions, evaluate JavaScript expressions, check data types, and perform other evaluative operations on your document fields. Mastering these operators significantly expands your query capabilities in MongoDB and allows you to implement complex filtering logic in your applications.
In this guide, we'll explore the main evaluation operators available in MongoDB and how they can be used in practical scenarios.
Key Evaluation Operators
MongoDB provides several evaluation operators to help you build complex queries:
Operator | Description |
---|---|
$regex | Matches fields against regular expression patterns |
$expr | Allows the use of aggregation expressions within queries |
$jsonSchema | Validates documents against a JSON Schema |
$mod | Performs a modulo operation on field values |
$text | Performs text search on string content |
$where | Uses JavaScript expressions for complex matching logic |
$type | Selects documents where field is a specific BSON type |
Let's explore each of these operators in detail.
The $regex
Operator
The $regex
operator allows you to match string fields using regular expressions, which is particularly useful for pattern matching.
Syntax
{ field: { $regex: pattern, $options: options } }
// or
{ field: /pattern/options }
Examples
Finding all products whose names contain "phone" (case insensitive):
db.products.find({ name: { $regex: /phone/i } })
Finding all users whose email ends with "gmail.com":
db.users.find({ email: { $regex: /gmail\.com$/i } })
Finding all documents with a description containing either "amazing" or "incredible":
db.reviews.find({
description: { $regex: /amazing|incredible/i }
})
Output
// For the query finding products containing "phone"
[
{ "_id": ObjectId("5f85a2f88c7ba17a13b73f1c"), "name": "Smartphone X10", "price": 699 },
{ "_id": ObjectId("5f85a3018c7ba17a13b73f1d"), "name": "iPhone 12 Pro", "price": 999 },
{ "_id": ObjectId("5f85a30a8c7ba17a13b73f1e"), "name": "Headphones for Phone", "price": 59 }
]
$regex
Options
i
: Case insensitivitym
: Multiline matchings
: Allows dot (.) to match newline charactersx
: Ignores whitespace in the pattern
Performance Considerations
- Prefix regular expressions (like
/^prefix/
) can use indexes - Non-prefix expressions force collection scans
- For case-insensitive searches on frequently queried fields, consider storing a lowercase copy of the field and indexing it
The $expr
Operator
The $expr
operator allows you to use aggregation expressions within query language, enabling comparisons between fields in the same document.
Syntax
{ $expr: { <expression> } }
Examples
Finding all sales documents where the amount sold exceeds the target:
db.sales.find({
$expr: { $gt: ["$actual", "$target"] }
})
Finding products where the discount price is at least 20% less than the original price:
db.products.find({
$expr: {
$gte: [
{ $divide: [
{ $subtract: ["$originalPrice", "$discountPrice"] },
"$originalPrice"
]
},
0.2
]
}
})
Output
// For the sales query where actual > target
[
{ "_id": ObjectId("5f8a3e1f8c7ba17a13b73f20"), "salesperson": "Alice", "actual": 12000, "target": 10000 },
{ "_id": ObjectId("5f8a3e2a8c7ba17a13b73f21"), "salesperson": "Bob", "actual": 9500, "target": 9000 }
]
The $type
Operator
The $type
operator selects documents where the value of a field is an instance of the specified BSON type(s).
Syntax
{ field: { $type: <BSON type> } }
// or
{ field: { $type: [ <BSON type1>, <BSON type2>, ... ] } }
BSON Types
MongoDB uses BSON (Binary JSON) for data storage. Some common BSON types include:
- 1: Double
- 2: String
- 3: Object
- 4: Array
- 8: Boolean
- 9: Date
- 16: Int32
- 18: Int64
You can specify types by number or by string name (e.g., "string", "double", "bool").
Examples
Finding all documents where the age field is a string (perhaps indicating a data quality issue):
db.users.find({ age: { $type: "string" } })
Finding products where the price is either an integer or a double:
db.products.find({ price: { $type: ["int", "double"] } })
Output
// For the query finding users where age is a string
[
{ "_id": ObjectId("5f8a4c1f8c7ba17a13b73f25"), "name": "John Doe", "age": "thirty" },
{ "_id": ObjectId("5f8a4c2a8c7ba17a13b73f26"), "name": "Jane Smith", "age": "25" }
]
Use Cases for $type
- Data validation and cleanup
- Finding potential data quality issues
- Handling different data representations in unstructured data
- Processing mixed data types in imported datasets
The $mod
Operator
The $mod
operator performs the modulo operation on a field's value and selects documents where the result matches a specified value.
Syntax
{ field: { $mod: [divisor, remainder] } }
Examples
Finding all users with even user IDs:
db.users.find({ user_id: { $mod: [2, 0] } })
Finding products to include in a "every third item free" promotion:
db.inventory.find({ product_id: { $mod: [3, 0] } })
Output
// For the query finding users with even IDs
[
{ "_id": ObjectId("5f8a5d1f8c7ba17a13b73f30"), "name": "Alice", "user_id": 2 },
{ "_id": ObjectId("5f8a5d2a8c7ba17a13b73f32"), "name": "Bob", "user_id": 4 },
{ "_id": ObjectId("5f8a5d358c7ba17a13b73f34"), "name": "Charlie", "user_id": 6 }
]
The $text
Operator
The $text
operator performs a text search on fields that have a text index. This is particularly useful for full-text search functionality.
Pre-requisite: Creating a Text Index
Before using $text
, you must create a text index on the field(s) you want to search:
db.articles.createIndex({ content: "text", title: "text" })
Syntax
{ $text: { $search: "search string", $language: "en", $caseSensitive: false, $diacriticSensitive: false } }
Examples
Finding all articles containing either "mongodb" or "database":
db.articles.find({ $text: { $search: "mongodb database" } })
Finding articles containing the exact phrase "document database":
db.articles.find({ $text: { $search: "\"document database\"" } })
Output
// For the query finding articles with "mongodb" or "database"
[
{
"_id": ObjectId("5f8a6a1f8c7ba17a13b73f40"),
"title": "Introduction to MongoDB",
"content": "MongoDB is a document database..."
},
{
"_id": ObjectId("5f8a6a2a8c7ba17a13b73f41"),
"title": "Choosing the Right Database",
"content": "When selecting a database system..."
}
]
Sorting by Text Score
You can sort the results by relevance using the $meta
operator:
db.articles.find(
{ $text: { $search: "mongodb database" } },
{ score: { $meta: "textScore" } }
).sort({ score: { $meta: "textScore" } })
The $where
Operator
The $where
operator allows you to pass a JavaScript expression or function to the query system. This provides maximum flexibility but comes with performance and security considerations.
Syntax
{ $where: "JavaScript expression" }
// or
{ $where: function() { return JavaScript expression; } }
Examples
Finding all products where the sum of ratings is greater than 100:
db.products.find({
$where: function() {
return Array.isArray(this.ratings) &&
this.ratings.reduce((sum, rating) => sum + rating, 0) > 100;
}
})
Finding users whose first and last names are the same:
db.users.find({
$where: "this.firstName.toLowerCase() === this.lastName.toLowerCase()"
})
Output
// For the query finding products with sum of ratings > 100
[
{
"_id": ObjectId("5f8a7b1f8c7ba17a13b73f50"),
"name": "Popular Gadget",
"ratings": [30, 45, 28, 17]
}
]
Important Considerations for $where
$where
uses JavaScript evaluation, which is significantly slower than other operators- It cannot use indexes effectively, leading to full collection scans
- It poses potential security risks if used with user-provided input
- Use other operators whenever possible for better performance
- Reserved for complex queries that cannot be expressed using other operators
The $jsonSchema
Operator
The $jsonSchema
operator allows you to validate documents against a specified JSON Schema. This is particularly useful for ensuring data quality and structure.
Syntax
{
$jsonSchema: {
bsonType: <type>,
required: [<field1>, <field2>, ...],
properties: {
<field1>: {<validation rules>},
<field2>: {<validation rules>},
...
}
}
}
Example
Finding all products that match a specific schema:
db.products.find({
$jsonSchema: {
bsonType: "object",
required: ["name", "price", "category"],
properties: {
name: { bsonType: "string", minLength: 3 },
price: { bsonType: "number", minimum: 0 },
category: { enum: ["electronics", "clothing", "food", "books"] }
}
}
})
This query finds all products that:
- Have required fields: name, price, and category
- Have a name that is a string with at least 3 characters
- Have a price that is a number greater than or equal to 0
- Have a category that is one of the specified values
Output
// For the jsonSchema query
[
{
"_id": ObjectId("5f8a8c1f8c7ba17a13b73f60"),
"name": "Smartphone",
"price": 599.99,
"category": "electronics"
},
{
"_id": ObjectId("5f8a8c2a8c7ba17a13b73f61"),
"name": "Winter Jacket",
"price": 89.95,
"category": "clothing"
}
]
Real-world Applications
User Search with Flexible Matching
Imagine building a user search feature that needs to handle partial matches, typing errors, and different name formats:
db.users.find({
$or: [
{ firstName: { $regex: new RegExp(searchTerm, "i") } },
{ lastName: { $regex: new RegExp(searchTerm, "i") } },
{ email: { $regex: new RegExp(searchTerm, "i") } }
]
})
Dynamic Price Filtering
For an e-commerce application where you need to find products with significant discounts:
db.products.find({
$expr: {
$gt: [
{ $subtract: ["$originalPrice", "$currentPrice"] },
{ $multiply: ["$originalPrice", 0.25] }
]
}
})
This query finds products where the discount amount is more than 25% of the original price.
Content Moderation System
For a content platform that needs to flag potentially problematic content:
db.posts.createIndex({ content: "text", title: "text" })
db.posts.find({
$text: {
$search: "offensive inappropriate explicit"
},
postDate: { $gte: new Date(Date.now() - 24 * 60 * 60 * 1000) }
})
This query finds recently posted content containing potentially problematic terms.
Data Quality Monitoring
For a system that needs to identify inconsistent data types across records:
// Find all users where age is not a number
db.users.find({
age: { $exists: true, $not: { $type: ["int", "double"] } }
})
Summary
MongoDB's evaluation operators provide a powerful toolkit for creating sophisticated queries:
$regex
enables pattern matching with regular expressions$expr
allows for field comparisons and calculations within the same document$type
helps identify and filter based on data types$mod
is useful for divisibility checks and cyclic patterns$text
provides full-text search capabilities (with proper indexing)$where
offers JavaScript evaluation for maximum flexibility$jsonSchema
validates documents against a JSON Schema
By leveraging these operators, you can implement complex filtering logic directly in your database queries, reducing the need for post-query processing in your application code.
Practice Exercises
- Write a query to find all documents where the email field is a valid email address (contains "@" and ends with a domain).
- Create a query to find all products that have a margin percentage (calculated as
(price - cost) / price
) greater than 40%. - Find all transactions where the amount is divisible by 5.
- Write a query to find user documents where the phone number is in an incorrect format (not a string or not following a specific pattern).
- Use
$text
search to find articles about MongoDB that don't mention "SQL".
Additional Resources
- MongoDB Documentation: Query Operators
- MongoDB Documentation: Regular Expressions
- MongoDB Documentation: Text Search
- MongoDB University: Free MongoDB Courses
Remember that while evaluation operators provide powerful capabilities, they should be used judiciously. Operations like $where
and complex $regex
patterns can have performance implications, especially on large collections. Always consider indexing strategies and query optimization when working with these operators in production environments.
If you spot any mistakes on this website, please let me know at feedback@compilenrun.com. I’d greatly appreciate your feedback! :)