🏠 Home / Hub

🔍 MongoDB — Lesson 4: Query Operators

MongoDB's query language (MQL) provides a rich set of operators for filtering, comparing, and searching documents. This lesson covers all major query operators with real examples using a products and users dataset.

Setup — Sample Data

use querylab

// Insert sample products
db.products.insertMany([
  { name: "Laptop Pro",     category: "electronics", price: 1299.99, stock: 15, tags: ["computer", "portable"],  rating: 4.5, active: true  },
  { name: "Wireless Mouse",  category: "electronics", price: 29.99,   stock: 200, tags: ["peripheral", "wireless"], rating: 4.1, active: true  },
  { name: "USB-C Hub",       category: "electronics", price: 49.99,   stock: 0,   tags: ["peripheral", "usb"],      rating: 3.8, active: false },
  { name: "Standing Desk",   category: "furniture",   price: 599.00,  stock: 8,   tags: ["office", "ergonomic"], rating: 4.7, active: true  },
  { name: "Desk Chair",      category: "furniture",   price: 299.00,  stock: 12,  tags: ["office", "ergonomic"], rating: 4.3, active: true  },
  { name: "Monitor 27\"",    category: "electronics", price: 449.00,  stock: 5,   tags: ["display", "4k"],        rating: 4.6, active: true  }
])

1. Comparison Operators

OperatorMeaningSQL Equivalent
$eqEqual to= value
$neNot equal to!= value
$gtGreater than> value
$gteGreater than or equal>= value
$ltLess than< value
$lteLess than or equal<= value
$inMatches any value in arrayIN (a, b, c)
$ninMatches no values in arrayNOT IN (a, b, c)
// $eq — explicit (usually you just write the value directly)
db.products.find({ category: { $eq: "electronics" } })
db.products.find({ category: "electronics" })   // same thing, shorthand

// $ne — not equal
db.products.find({ category: { $ne: "furniture" } })

// $gt / $gte / $lt / $lte — ranges
db.products.find({ price: { $gt: 100 } })               // price > 100
db.products.find({ price: { $gte: 100, $lte: 500 } })   // 100 <= price <= 500
db.products.find({ stock: { $gt: 0 } })                 // in stock

// Works on dates too
db.orders.find({
  createdAt: {
    $gte: new Date("2024-01-01"),
    $lt:  new Date("2024-02-01")
  }
})

// $in — match one of many values
db.products.find({ category: { $in: ["electronics", "furniture"] } })
db.products.find({ rating: { $in: [4.5, 4.6, 4.7] } })

// $nin — match none of these values
db.products.find({ category: { $nin: ["furniture", "clothing"] } })

2. Logical Operators

// $and — all conditions must be true (implicit AND by default)
db.products.find({
  $and: [
    { category: "electronics" },
    { price: { $lt: 100 } },
    { active: true }
  ]
})
// Same as (implicit AND):
db.products.find({ category: "electronics", price: { $lt: 100 }, active: true })

// Explicit $and is needed when using same field twice
db.products.find({
  $and: [
    { price: { $gte: 50 } },
    { price: { $lte: 500 } }
  ]
})

// $or — at least one condition must be true
db.products.find({
  $or: [
    { price: { $lt: 50 } },
    { category: "furniture" }
  ]
})

// Combine $and and $or
db.products.find({
  active: true,
  $or: [
    { price: { $lt: 100 } },
    { rating: { $gte: 4.5 } }
  ]
})

// $nor — none of the conditions must be true
db.products.find({
  $nor: [
    { category: "furniture" },
    { price: { $gt: 1000 } }
  ]
})
// Returns products that are NOT furniture AND NOT over $1000

// $not — negates a single condition
db.products.find({ price: { $not: { $gt: 500 } } })  // price NOT > 500
db.products.find({ name: { $not: /Laptop/i } })       // name does NOT match regex

3. Element Operators

// $exists — check if a field exists (or doesn't exist)
db.products.find({ discount: { $exists: true } })   // has a discount field
db.products.find({ discount: { $exists: false } })  // no discount field

// $exists: true also matches null values
// Use with $ne to exclude null:
db.products.find({ discount: { $exists: true, $ne: null } })

// $type — filter by BSON type
db.products.find({ price: { $type: "double" } })     // price is a float
db.products.find({ price: { $type: "int" } })        // price is an integer
db.products.find({ name: { $type: "string" } })      // name is a string
db.products.find({ _id: { $type: "objectId" } })    // _id is ObjectId
db.products.find({ tags: { $type: "array" } })       // tags field is an array

// $type with multiple types
db.mixed.find({ value: { $type: ["string", "double"] } })

4. Array Operators

// Match documents where array contains a value
db.products.find({ tags: "wireless" })             // tags array includes "wireless"

// $all — array must contain ALL specified values
db.products.find({ tags: { $all: ["office", "ergonomic"] } })
// returns docs where tags contains BOTH "office" AND "ergonomic"

// $size — array has exact number of elements
db.products.find({ tags: { $size: 2 } })   // exactly 2 tags

// $elemMatch — at least one element matches ALL conditions
db.orders.find({
  items: {
    $elemMatch: {
      price: { $gt: 100 },
      quantity: { $gte: 2 }
    }
  }
})
// Finds orders where a single item has price > 100 AND quantity >= 2

// Without $elemMatch (wrong — applies conditions across elements)
db.orders.find({
  "items.price": { $gt: 100 },
  "items.quantity": { $gte: 2 }
})
// This could match if different items satisfy each condition!

// Query array element by position
db.products.find({ "tags.0": "computer" })  // first tag is "computer"

5. Evaluation Operators

$regex — Pattern Matching

// Case-insensitive search for "laptop" in name
db.products.find({ name: { $regex: "laptop", $options: "i" } })

// Inline regex literal
db.products.find({ name: /^Wireless/i })   // starts with "Wireless"
db.products.find({ name: /Hub$/ })           // ends with "Hub"
db.products.find({ category: /electron/i }) // contains "electron"

// Regex options:
// i - case insensitive
// m - multiline (^ and $ match line boundaries)
// x - extended (ignore whitespace)
// s - allows . to match newlines

$text — Full-Text Search

// First: create a text index on the fields to search
db.products.createIndex({ name: "text", description: "text" })

// Then: use $text to search
db.products.find({ $text: { $search: "laptop computer" } })

// Exact phrase search (use quotes)
db.products.find({ $text: { $search: "\"standing desk\"" } })

// Exclude word (prefix with -)
db.products.find({ $text: { $search: "desk -chair" } })

// Sort by text relevance score
db.products.find(
  { $text: { $search: "wireless" } },
  { score: { $meta: "textScore" } }
).sort({ score: { $meta: "textScore" } })

$expr — Use Aggregation Expressions in Queries

// Find products where discountedPrice < originalPrice * 0.8
db.products.find({
  $expr: { $lt: ["$discountedPrice", { $multiply: ["$price", 0.8] }] }
})

// Compare two fields in the same document
db.orders.find({
  $expr: { $gt: ["$total", "$budget"] }
})
// Finds orders where total exceeds the customer's budget

6. Projection — Controlling Output Fields

// Include specific fields (1 = include)
db.products.find({}, { name: 1, price: 1 })
// Returns: { _id, name, price } — _id is always included unless excluded

// Exclude _id
db.products.find({}, { name: 1, price: 1, _id: 0 })
// Returns: { name, price }

// Exclude specific fields (0 = exclude)
db.products.find({}, { description: 0, __v: 0 })
// Returns all fields EXCEPT description and __v

// Project array slice
db.posts.find({}, { title: 1, comments: { $slice: 3 } })
// Returns first 3 comments only

db.posts.find({}, { title: 1, comments: { $slice: [-5, 5] } })
// Returns last 5 comments

// $elemMatch in projection — return only matching array element
db.products.find(
  { category: "electronics" },
  {
    name: 1,
    tags: { $elemMatch: { $eq: "wireless" } }
  }
)

// Project nested fields
db.users.find({}, { "profile.firstName": 1, "profile.avatar": 1 })

7. Cursor Methods

// sort() — ascending (1), descending (-1)
db.products.find().sort({ price: 1 })           // cheapest first
db.products.find().sort({ price: -1 })          // most expensive first
db.products.find().sort({ category: 1, price: -1 }) // by category, then price desc

// skip() + limit() — pagination
const page = 2
const perPage = 10
db.products.find({ active: true })
           .sort({ name: 1 })
           .skip((page - 1) * perPage)
           .limit(perPage)

// count() — count matching documents
db.products.countDocuments({})                    // exact count
db.products.countDocuments({ active: true })      // filtered count
db.products.estimatedDocumentCount()               // fast metadata count

// distinct() — get unique values for a field
db.products.distinct("category")
// ["electronics", "furniture"]

db.products.distinct("tags")
// ["computer", "display", "ergonomic", "office", "peripheral", ...]

// forEach() — iterate over cursor results
db.products.find({ active: true }).forEach((doc) => {
  print(`${doc.name}: $${doc.price}`)
})

// map() — transform cursor to array
const names = db.products.find().map(doc => doc.name)

8. Complete Query Operators Reference

OperatorTypeDescription
$eqComparisonMatches values equal to specified value
$neComparisonMatches values not equal to specified value
$gtComparisonMatches values greater than specified value
$gteComparisonMatches values >= specified value
$ltComparisonMatches values less than specified value
$lteComparisonMatches values <= specified value
$inComparisonMatches any value in the given array
$ninComparisonMatches none of the values in the given array
$andLogicalAll conditions must be true
$orLogicalAt least one condition must be true
$norLogicalNone of the conditions must be true
$notLogicalInverts the effect of a query expression
$existsElementMatches documents that have (or don't have) a field
$typeElementMatches documents where field is a specified BSON type
$allArrayArray contains all specified elements
$elemMatchArrayAt least one element matches all specified criteria
$sizeArrayArray has specified number of elements
$regexEvaluationValue matches a regular expression
$textEvaluationFull-text search using a text index
$exprEvaluationAllows aggregation expressions within a query
$whereEvaluationJavaScript expression (slow — use $expr instead)
$modEvaluationModulo operation — {field: {$mod: [divisor, remainder]}}
$jsonSchemaEvaluationValidates documents against a JSON Schema
$geoWithinGeospatialGeometries within a shape (requires 2dsphere index)
$nearGeospatialGeometries near a point
$bitsAllSetBitwiseAll specified bits are set in the field
$sliceProjectionReturns a subset of array elements
$metaProjectionReturns metadata (e.g., text search score)
$ProjectionReturns first matching array element

📌 Study Checklist