Querying & Aggregation
Master query operators, array queries, the aggregation pipeline, and $lookup joins.
Comparison and Logical Operators
Query filters use operators such as $eq, $ne, $gt, $gte, $lt, $lte, and $in to express conditions. Combine predicates with $and, $or, and $nor for complex logic.
Remember that MongoDB treats null and missing fields differently in some operators. Use $exists to explicitly filter on field presence when that distinction matters.
db.orders.find({
total: { $gte: 100, $lte: 500 },
status: { $in: ["paid", "shipped"] },
$or: [{ region: "EU" }, { priority: "express" }]
})Array and Element Operators
Array fields support operators like $all, $elemMatch, and $size. $elemMatch matches array elements that satisfy multiple conditions on the same element, which is essential for arrays of subdocuments.
Dot notation queries nested fields (address.city). For arrays of scalars, a simple match on the field finds documents where any element equals the value.
- $size matches exact array length but cannot use ranges efficiently without redesign
- Use $[] or $[<identifier>] in updates for positional array modifications
- Avoid unbounded arrays; cap or bucket large collections of embedded items
db.products.find({
tags: { $all: ["sale", "featured"] },
reviews: { $elemMatch: { rating: { $gte: 4 }, verified: true } }
})Aggregation Pipeline Stages
The aggregation framework processes documents through a pipeline of stages. Common stages include $match (filter), $group (aggregate), $project (reshape), $sort, $limit, and $unwind (flatten arrays).
Place $match as early as possible so later stages process fewer documents. Use $group for analytics such as totals, averages, and counts grouped by category or date.
db.orders.aggregate([
{ $match: { status: "paid", createdAt: { $gte: ISODate("2024-01-01") } } },
{ $group: { _id: "$customerId", totalSpent: { $sum: "$total" }, orderCount: { $sum: 1 } } },
{ $sort: { totalSpent: -1 } },
{ $limit: 10 }
])$lookup Joins
MongoDB is not join-free—you can join collections in aggregation with $lookup. This performs a left outer join from the local collection to a foreign collection, similar to SQL LEFT JOIN.
For high-volume join patterns, consider embedding or denormalizing at write time instead. $lookup is powerful for reporting and admin queries but can be expensive on large collections without proper indexes on the foreign field.
db.orders.aggregate([
{ $match: { status: "shipped" } },
{
$lookup: {
from: "customers",
localField: "customerId",
foreignField: "_id",
as: "customer"
}
},
{ $unwind: "$customer" },
{ $project: { orderId: 1, "customer.name": 1, total: 1 } }
])Text Search and Indexes
Create a text index on string fields to support $text queries with relevance scoring. Text search is useful for user-facing search boxes but lacks the flexibility of dedicated search engines like Elasticsearch for fuzzy matching and faceting.
Combine text search with compound indexes for filtered search (e.g., search within a category). Always verify index usage with explain on representative queries.
db.articles.createIndex({ title: "text", body: "text" })
db.articles.find(
{ $text: { $search: "mongodb aggregation" }, category: "database" },
{ score: { $meta: "textScore" } }
).sort({ score: { $meta: "textScore" } })