Query Operators: A Complete Guide to the 30+ Operator
Query operators are at the heart of MongoDB queries—mastering 30+ operators can handle 90% of query scenarios.
This course provides a systematic overview of the usage, combinations, and performance optimization techniques for all query operators.
1. What You'll Learn
- Comparison operators ($eq/$ne/$gt/$lt/$gte/$lte/$in/$nin)
- Logical operators ($and/$or/$not/$nor)
- Element operators ($exists/$type)
- Array operators ($all/$size/$elemMatch)
- Evaluation operators ($regex/$expr/$mod)
- Operator Combination and Its Impact on Performance
2. A Data Analyst’s True Story
(1) Pain Point: Complex queries require nested if-else statements
Diana is an e-commerce data analyst who needs to find products that meet the following criteria: "price 100–500, inventory > 0, tags include 'bestseller' or 'new,' and rating >= 4":
// ❌ Counterexample:Query everything first, then filter
const allProducts = await Product.find();
const filtered = allProducts.filter(p =>
p.price >= 100 && p.price <= 500 &&
p.stock > 0 &&
(p.tags.includes('bestseller') || p.tags.includes('new')) &&
p.rating >= 4
);
// 100,000 Data Points, Return only 100 items,But after checking all of them
(2) Solutions for MongoDB Operators
// ✅ Correct Example:A single query,Combining Operators
const products = await Product.find({
price: { $gte: 100, $lte: 500 },
stock: { $gt: 0 },
tags: { $in: ['bestseller', 'new'] },
rating: { $gte: 4 }
});
// Database-Level Filtering,1 million -> 100 items,Performance ↑1000x
graph TB
A[Query Operators] --> B[Comparison Operators<br/>$eq/$gt/$lt]
A --> C[Logical Operators<br/>$and/$or/$not]
A --> D[Element Operators<br/>$exists/$type]
A --> E[Array Operators<br/>$all/$size/$elemMatch]
A --> F[Evaluation Operator<br/>$regex/$expr/$mod]
style B fill:#d4edda
style E fill:#d4edda
3. Comparison Operators
Concept Overview: Comparison operators are the most basic and commonly used category of query operators, supporting operations such as equality, inequality, greater than, less than, range, and multi-value matching. The eight comparison operators cover 90% of everyday query scenarios, with $in and range queries (the $gt/$gte/$lt/$lte combination) being the most frequently used.
How It Works: Comparison operators act on a single field; MongoDB translates them into an index scan or a full-table scan. $eq and $in are the most index-friendly (supporting exact matches or multi-value scans); range queries ($gt/$lt) can also use indexes, but efficiency depends on the range size. $ne and $nin are not index-friendly—because documents that “are not equal to a certain value” may be scattered throughout the index.
graph TB
A[Comparison Operators] --> B[Equivalence Class<br/>$eq / $ne]
A --> C[Scope Class<br/>$gt / $gte / $lt / $lte]
A --> D[Multi-valued types<br/>$in / $nin]
B --> B1[Index Friendliness: $eq ✅ / $ne ⚠️]
C --> C1[Index Friendliness: ✅ Range Scan]
D --> D1[Index Friendliness: $in ✅ / $nin ⚠️]
| Operator | Meaning | Equivalent SQL | Index-Friendly | Frequency of Use |
|---|---|---|---|---|
$eq |
Equals | = value |
✅ | ⭐⭐⭐ (Default) |
$ne |
Not equal to | != value |
⚠️ | ⭐ |
$gt |
Greater than | > value |
✅ | ⭐⭐ |
$gte |
Greater than or equal to | >= value |
✅ | ⭐⭐⭐ |
$lt |
Smaller than | < value |
✅ | ⭐⭐ |
$lte |
Less than or equal to | <= value |
✅ | ⭐⭐⭐ |
$in |
Included in the array | IN (...) |
✅ | ⭐⭐⭐ |
$nin |
Not included | NOT IN (...) |
⚠️ | ⭐ |
(1) Complete List
| Operator | Meaning | Example |
|---|---|---|
$eq |
equals | { price: { $eq: 599 } } |
$ne |
is not equal to | { status: { $ne: 'deleted' } } |
$gt |
Greater than | { age: { $gt: 18 } } |
$gte |
Greater than or equal to | { rating: { $gte: 4 } } |
$lt |
Smaller than | { stock: { $lt: 10 } } |
$lte |
Less than or equal to | { discount: { $lte: 0.5 } } |
$in |
Included in the array | { category: { $in: ['A', 'B'] } } |
$nin |
Not included in the array | { status: { $nin: ['deleted', 'banned'] } } |
▶ Example 1: Comparison Operators in Practice
// === Price Range ===
db.products.find({
price: { $gte: 100, $lte: 500 } // 100 <= price <= 500
});
// === Multi-value matching ===
db.products.find({
category: { $in: ['Electronics', 'Books'] }
});
// === Exclude Specific Values ===
db.users.find({
role: { $nin: ['banned', 'deleted'] }
});
4. Logical Operators
Concept Explanation: Logical operators combine multiple query conditions to implement AND/OR/NOT logic. The most common logic in MongoDB is implicit AND—{ a: 1, b: 2 } is equivalent to { $and: [{ a: 1 }, { b: 2 }] }. Explicit $and must only be used when there are "multiple conditions on the same field" (e.g., { $and: [{ price: { $gte: 100 } }, { price: { $lte: 500 } }] }).
How It Works: $and requires that all sub-conditions be satisfied simultaneously; the MongoDB optimizer will prioritize executing the most selective condition. $or requires that any one sub-condition be satisfied; MongoDB executes queries for each sub-condition independently and then merges the result sets—which means it is best to have an index on each sub-condition in $or, otherwise multiple full table scans will be triggered.
graph TB
A[Logical Operators] --> B[Implicit AND<br/>{ a: 1, b: 2 }<br/>Most Commonly Used ⭐⭐⭐]
A --> C[$and<br/>Multiple Conditions for the Same Field]
A --> D[$or<br/>Any that satisfies ⭐⭐]
A --> E[$not<br/>Negate ⭐]
A --> F[$nor<br/>None of the conditions are met]
style B fill:#d4edda
| Operator | Meaning | Equivalent SQL | Notes |
|---|---|---|---|
| Implicit AND | Comma-separated | WHERE a=1 AND b=2 |
Most Common Notation |
$and |
Explicit AND | WHERE (a=1 AND b=2) |
Must use for multiple conditions on the same field |
$or |
Any one that satisfies | WHERE a=1 OR b=2 |
Each subcondition should have an index |
$not |
Not compatible | WHERE NOT (cond) |
Often used in conjunction with $regex |
$nor |
None met | WHERE NOT (a=1 OR b=2) |
Underused |
(1) Complete List
| Operator | Meaning |
|---|---|
$and |
All met |
$or |
Any that satisfies |
$not |
Not satisfied |
$nor |
None met |
▶ Example 2: Practical Application of Logical Operators
// === Implicit $and ===
db.products.find({
category: 'Electronics',
stock: { $gt: 0 }
// equivalent to { $and: [{ category: 'Electronics' }, { stock: { $gt: 0 } }] }
});
// === Explicit $and(Multiple Conditions for the Same Field)===
db.products.find({
$and: [
{ price: { $gte: 100 } },
{ price: { $lte: 500 } }
// Must use $and,Because you cannot specify multiple conditions for the same field directly
]
});
// === $or ===
db.products.find({
$or: [
{ category: 'Electronics' },
{ tags: 'bestseller' }
]
});
// === $nor(None of them match)===
db.products.find({
$nor: [
{ category: 'Books' },
{ stock: 0 }
]
});
// Return is neither Books Categories、Items Currently in Stock
// === Nested Logic ===
db.products.find({
$and: [
{ category: 'Electronics' },
{
$or: [
{ tags: 'bestseller' },
{ rating: { $gte: 4.5 } }
]
}
]
});
5. Element Operators
Concept Explanation: Element operators check the existence and type of fields in a document—$exists determines whether a field exists, and $type determines the BSON type of a field. These two operators are crucial for data quality checks and cleaning up dirty data: $exists can identify documents with missing required fields, and $type can identify fields with incorrect types (such as price being incorrectly stored as a string instead of a number).
How It Works: $exists: true matches all documents that contain the specified field (including documents where the field value is null), while $exists: false matches documents that do not contain that field. $type matches based on BSON type codes or type names and supports array formats (e.g., { price: { $type: ["number", "decimal"] } } matches Double or Decimal128).
| Operator | Meaning | Typical Scenarios |
|---|---|---|
$exists: true |
Field exists | Find products with a "discount" field |
$exists: false |
Field does not exist | Find users with missing email addresses |
$type: "string" |
Field type is string | Find documents where "price" was incorrectly stored as a string |
$type: ["number", "decimal"] |
One of several types | Find documents where price is of any numeric type |
(1) $exists checks whether a field exists
// === Field exists ===
db.products.find({ discount: { $exists: true } });
// Have discount Products in the Field
// === Field does not exist ===
db.users.find({ lastLoginAt: { $exists: false } });
// Users who have never logged in
// === In conjunction with other conditions ===
db.products.find({
discount: { $exists: true, $ne: null }
});
// Have discount Let's not worry about that for now null
(2) $type: Check the field type
// === Type Code Reference ===
// 1: Double, 2: String, 3: Object, 4: Array
// 5: Binary, 7: ObjectId, 8: Boolean, 9: Date
// 10: Null, 16: Int32, 18: Long, 19: Decimal128
// === String Type ===
db.products.find({ sku: { $type: "string" } });
// === Numeric Types(Various)===
db.products.find({ price: { $type: ["number", "decimal"] } });
// Double or Decimal128
// === Practical Applications:Identify fields with type errors ===
db.products.find({ price: { $type: "string" } });
// Find price Documents in Which Fields Were Incorrectly Stored as Strings(Frequently Asked Questions About Data Migration)
▶ Example 3: Element Operators in Action
// === Find "dirty" data with missing fields ===
db.users.find({
$or: [
{ email: { $exists: false } },
{ username: { $exists: false } }
]
});
// === Type Correction ===
db.products.find({ price: { $type: "string" } }).forEach(doc => {
db.products.updateOne(
{ _id: doc._id },
{ $set: { price: NumberDecimal(doc.price) } }
);
});
6. Array Operators
Concept Explanation: Array operators are the most unique category of MongoDB query operators, specifically designed to handle array fields—$all matches arrays containing all specified elements, $size matches arrays of a specific length, and $elemMatch matches a single element in an array that satisfies multiple conditions simultaneously. Among these, $elemMatch is the most error-prone and also the most important array operator.
How It Works: By default, MongoDB’s array queries use “any element match”—{ tags: "5g" } matches documents where the tags array contains “5g.” However, in multi-condition queries, { "reviews.rating": { $gte: 4 }, "reviews.content": /good/ } may match documents with different array elements (one comment has a high rating, while another contains a keyword). $elemMatch solves this problem—it ensures that the same array element satisfies all conditions simultaneously.
graph TB
A[Array Operators] --> B[$all<br/>Includes all specified elements]
A --> C[$size<br/>Exact Match for Array Length]
A --> D[$elemMatch<br/>Multi-Condition Matching for the Same Element<br/>⭐ Most important]
D --> D1[Dot notation<br/>Different elements may match]
D --> D2[$elemMatch<br/>The same element must match]
style D fill:#d4edda
style D2 fill:#d4edda
| Operator | Meaning | Syntax | Notes |
|---|---|---|---|
$all |
Includes all specified elements | { tags: { $all: ["5g", "amoled"] } } |
Order does not matter |
$size |
Exact match for array length | { tags: { $size: 3 } } |
Does not support ranges; use $expr |
$elemMatch |
Multiple-condition matching for the same element | { reviews: { $elemMatch: { ... } } } |
Distinction from dot notation |
(1) $all matches all elements
// === Must include all specified elements(Order does not matter)===
db.products.find({ tags: { $all: ['5g', 'amoled'] } });
// Includes both 5g and amoled Products
// === equivalent to $and ===
db.products.find({
$and: [
{ tags: '5g' },
{ tags: 'amoled' }
]
});
(2) $size matches the length of the array
// === Exact Match for Array Length ===
db.products.find({ tags: { $size: 3 } });
// The number of tags is exactly 3
// === Unsupported Ranges,Required $expr ===
db.products.find({
$expr: { $gt: [{ $size: "$tags" }, 3] }
});
// Number of tags > 3
(3) $elemMatch Element Match (Key!)
// === Scene:Check the ratings in the reviews >= 4 and contains the keyword "good" Comments on ===
db.products.find({
reviews: {
$elemMatch: {
rating: { $gte: 4 },
content: /good/i
}
}
});
// ✅ Correct:A single comment meets both conditions at the same time
// === Counterexample:Do not use $elemMatch ===
db.products.find({
"reviews.rating": { $gte: 4 },
"reviews.content": /good/i
});
// ⚠️ Error:May match different comments(One comment, high rating,Another post containing the keyword)
▶ Example 4: Array Operators in Action
// === Scene:Student Course Registration System ===
db.students.insertMany([
{
name: "Alice",
courses: [
{ name: "Math", score: 85 },
{ name: "Physics", score: 92 },
{ name: "Chemistry", score: 78 }
]
},
// ...
]);
// Search:Selected Math And the score >= 90 students
db.students.find({
courses: {
$elemMatch: { name: "Math", score: { $gte: 90 } }
}
});
// Search:Selected 3 Students enrolled in courses at the undergraduate level or above
db.students.find({
$expr: { $gte: [{ $size: "$courses" }, 3] }
});
// Search:All course grades are >= 80 students
db.students.find({
courses: {
$not: {
$elemMatch: { score: { $lt: 80 } }
}
}
});
7. Evaluation Operators
Concept Explanation: Evaluation operators provide more flexible query capabilities—$regex supports regular expression matching (fuzzy search), $expr supports comparisons between fields using aggregate expressions, and $mod supports modulo operations. These operators are powerful but have poor performance and should be used as a "last resort"—index-friendly query methods should be prioritized.
How It Works: $regex By default, indexing is not supported (except for prefix-anchored queries ^pattern), so all documents must be scanned to perform regular expression matching. $expr When using aggregation pipeline expressions, the result of the expression is calculated for each document before comparison, which also results in poor performance. Neither of these should be used as the primary condition for high-frequency queries.
| Operator | Meaning | Index-Friendly | Performance | Use Cases |
|---|---|---|---|---|
$regex |
Regular expression matching | ⚠️ Prefix-only matching | Slow | Fuzzy search |
$expr |
Aggregate expression | ❌ | Very slow | Comparison between fields |
$mod |
Modulo Operation | ❌ | Slow | Modulo Filtering |
(1) $regex Regular Expression
// === Simple Regular Expressions ===
db.products.find({ title: /^iPhone/ });
// Title: "iPhone" Introduction
// === Case-insensitive ===
db.products.find({ title: { $regex: 'phone', $options: 'i' } });
// === Multimode Matching ===
db.products.find({
title: { $regex: 'phone|mobile', $options: 'i' }
});
// === Performance Optimization:Regular Expression Anchoring(Avoid ^.* Wildcard)===
// ✅ Fast: db.products.find({ title: /^iPhone/ })
// ⚠️ Slow: db.products.find({ title: /iPhone/ })
(2) $expr uses an aggregate expression
// === Comparison Between Fields ===
db.products.find({
$expr: { $gt: ["$discount", "$price"] }
});
// discount > price(Outlier Data)
// === Comparing Array Lengths ===
db.products.find({
$expr: { $gte: [{ $size: "$tags" }, 5] }
});
// === String Operations ===
db.products.find({
$expr: { $eq: [{ $substr: ["$title", 0, 5] }, "Hello"] }
});
(3) $mod: Modulo
// === Field-value pairs N Removing the Mold ===
db.products.find({ price: { $mod: [100, 0] } });
// The price can be 100 Divisibility by an Integer
// === Price Comparison 100 Removing the Mold = 50 ===
db.products.find({ price: { $mod: [100, 50] } });
8. Comprehensive Practical Training
Concept Explanation: In real-world business scenarios, queries rarely use a single operator—they are typically combinations of multiple operators. The key principles for combined queries are: place the most selective conditions first (to reduce the amount of data scanned by subsequent conditions), create indexes on the fields in all $or clauses, and avoid using complex aggregate expressions in $expr.
Principles of Performance Optimization:
- Equal value queries (
$eq/$in) have the highest selectivity and should be placed first in the filter. - Range queries (
$gt/$lt) are the second most selective and should be placed after equality conditions. - Each clause of
$orshould have independent index support $regexIndexes can only be used for prefix-based lookups; in all other cases, a full-table scan is performed.$exprHas the worst performance; use as a last resort
graph TB
A[Comprehensive Query Optimization] --> B[1. Equivalence Filtering<br/>Highest selectivity]
A --> C[2. Range Filtering<br/>Selectivity is secondary]
A --> D[3. Logic Combinations<br/>$and/$or]
A --> E[4. Sort + Pagination<br/>sort + limit]
B --> B1[{ category: 'E' }<br/>Index Scan]
C --> C1[{ price: { $gte: 100 } }<br/>Range Scan]
D --> D1[Combination of Multiple Conditions<br/>Merge Result Sets]
E --> E1[Index Sorting<br/>Avoid Memory Sorting]
style B1 fill:#d4edda
| Optimization Strategy | Query Method | Index Requirements | Performance |
|---|---|---|---|
| Optimal | Equi-value + Index Sort | Composite Index | O(log N) |
| Good | Equi-value + Range + Index | Composite Index | O(log N + K) |
| General | $or + indexes of each clause | Multiple independent indexes | O(K * log N) |
| Poor | $regex prefix anchoring | prefix index | O(log N + K) |
| Worst Case | $expr / Unindexed Sort | None | O(N log N) |
(1) Examples of Complex Queries
// === Scene:Advanced Search for E-commerce ===
// Price 100-1000、Electronics or Computers、Inventory > 0、
// Tags include 'bestseller' or 'new'、Rating >= 4、Have discount
db.products.find({
$and: [
{ price: { $gte: 100, $lte: 1000 } },
{
$or: [
{ category: 'Electronics' },
{ category: 'Computers' }
]
},
{ stock: { $gt: 0 } },
{ tags: { $in: ['bestseller', 'new'] } },
{ rating: { $gte: 4 } },
{ discount: { $exists: true, $ne: null } }
]
});
▶ Example 5: Operator Performance Comparison
// === Performance Testing:100 10,000 Documents ===
// ❌ Slow: Unindexed fields + $exists
db.users.find({ lastLoginAt: { $exists: true } });
// ~500ms(Full Table Scan)
// ✅ Fast: Index Field + Range Query
db.users.find({ age: { $gte: 18, $lt: 30 } });
// ~5ms(Index Scan)
// ❌ Slow: Regular expression not anchored
db.products.find({ title: /phone/i });
// ~300ms(Full Table Scan + Regular Expression Matching)
// ✅ Fast: Regular Expression Anchoring
db.products.find({ title: /^iPhone/ });
// ~10ms(Prefix Index)
❓ FAQ
{ field: { $in: [A, B] } } is equivalent to { $or: [{ field: A }, { field: B }] }. They have the same performance, but $in is recommended because it is more concise.{ "a.b": 1, "a.c": 2 } may match different array elements for b and c; { a: { $elemMatch: { b: 1, c: 2 } } } requires that the same element satisfy both conditions simultaneously.^pattern). Non-anchored regular expressions require scanning all documents to perform the match, resulting in poor performance.📖 Summary
- Comparison operators: $eq/$ne/$gt/$lt/$gte/$lte/$in/$nin
- Logical operators: $and (implicit) / $or / $not / $nor
- Element operators: $exists (field exists) / $type (field type)
- Array operators: $all (contains all) / $size (length) / $elemMatch (matches an element)
- Evaluation operators: $regex (regular expression) / $expr (aggregate expression) / $mod (modulo)
- Performance Optimization: Indexed Field Queries / Regular Expression Prefixes / Avoiding $expr
📝 Exercises
- Basic Question (⭐): Query Electronics products with prices between 100 and 500 and inventory greater than 0.
- Basic Question (⭐): Use $in to query products across multiple categories (Electronics/Books/Clothing).
- Advanced Problem (⭐⭐): Use $elemMatch to find products in the comments that have a "rating >= 4 and contain the keyword 'good'."
- Advanced Problem (⭐⭐): Use $expr to query products with more than 3 tags and a rating of 4 or higher.
- Challenge Question (⭐⭐⭐): Build complex queries (combinations of 5 or more operators), including AND/OR/NOT, $elemMatch, and $regex anchors, to test performance differences.



