← Back to MongoDB Mastery
Intermediate25 min read

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" } })

Get In Touch


Ready to discuss your next project? Drop me a message.