MongoDBDatabases

Querying MongoDB: Filters, Projections, Sorting & Cursor Methods

Master MongoDB querying: comparison and logical operators, array queries, regex, projections, sort and pagination, and cursor methods. Lesson 5 of MongoDB & NoSQL Mastery.

TT
Sarah Mitchell
6 min read
Querying MongoDB: Filters, Projections, Sorting & Cursor Methods

The find() method is the gateway to MongoDB's data. A well-constructed query reads only the documents and fields it needs — reducing network transfer, memory pressure, and the work the application has to do. This lesson covers the full query language: every operator category, projections, cursor methods, and the patterns that keep queries fast.

Previous: Lesson 4 — Schema Design & Data Modelling


The Query Filter Document

A filter document is a JSON-like object whose fields match against document fields. An empty filter {} matches all documents.

javascript
// Equality filters
db.orders.find({ status: "shipped" })
db.orders.find({ status: "shipped", customerId: ObjectId("user_123") })

// Nested field query (dot notation)
db.products.find({ "specs.connectivity": "Bluetooth" })

// Array element query — matches if the array contains this value
db.products.find({ tags: "wireless" })

Comparison Operators

OperatorMeaning
$eqEqual (implicit when you write { field: value })
$neNot equal
$gtGreater than
$gteGreater than or equal
$ltLess than
$lteLess than or equal
$inMatches any value in array
$ninMatches no value in array
javascript
// Orders placed in the last 7 days
const since = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000)
db.orders.find({ createdAt: { $gte: since } })

// Products priced between £20 and £100
db.products.find({ price: { $gte: 20, $lte: 100 } })

// Orders in specific statuses
db.orders.find({ status: { $in: ["processing", "shipped"] } })

// Orders not yet delivered or cancelled
db.orders.find({ status: { $nin: ["delivered", "cancelled"] } })

Logical Operators

javascript
// Implicit AND: all conditions in filter must match
db.products.find({ brand: "Logitech", price: { $lt: 100 } })

// Explicit $and (required when same field appears in both conditions)
db.products.find({ $and: [
  { price: { $gte: 40 } },
  { price: { $lt: 100 } }
]})

// $or: at least one condition matches
db.products.find({ $or: [
  { brand: "Logitech" },
  { brand: "Keychron" }
]})

// $nor: no conditions match
db.products.find({ $nor: [
  { stock: 0 },
  { price: { $gt: 200 } }
]})

// $not: negates a single operator expression
db.products.find({ price: { $not: { $gte: 100 } } })

// Combining $and and $or
db.products.find({
  $and: [
    { price: { $lt: 150 } },
    { $or: [{ brand: "Logitech" }, { brand: "Keychron" }] }
  ]
})

Element Operators

javascript
// Field exists (not absent)
db.products.find({ discount: { $exists: true } })

// Field does not exist
db.products.find({ discount: { $exists: false } })

// Field is null or missing
db.products.find({ discount: null })

// Match by BSON type
db.products.find({ price: { $type: "double" } })
db.products.find({ createdAt: { $type: "date" } })
db.products.find({ tags: { $type: "array" } })

Array Operators

javascript
// Array contains a specific element
db.products.find({ tags: "wireless" })

// Array contains ALL specified elements (order-independent)
db.products.find({ tags: { $all: ["electronics", "wireless"] } })

// Array has a specific length
db.products.find({ tags: { $size: 3 } })

// $elemMatch — at least one array element matches ALL conditions
// (needed when multiple conditions must apply to the same element)
db.orders.find({
  items: { $elemMatch: { productId: ObjectId("prod_001"), qty: { $gte: 2 } } }
})

// Querying by array index position
db.products.find({ "tags.0": "electronics" })

Regular Expressions

javascript
// Case-insensitive name search
db.products.find({ name: { $regex: /keyboard/i } })

// Starts with "Wire"
db.products.find({ name: { $regex: /^Wire/ } })

// Ends with "Hub"
db.products.find({ name: /Hub$/ })

// Prefer regex anchors (^ and $) and indexes over unanchored patterns
// Only anchored regex patterns can use indexes efficiently

Projections: Returning Only What You Need

Projections dramatically reduce network transfer and deserialization cost in application code.

javascript
// Inclusion projection — return only listed fields (plus _id)
db.products.find({}, { name: 1, price: 1 })

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

// Exclusion projection — return everything except listed fields
db.products.find({}, { specs: 0, tags: 0 })

// You cannot mix inclusion and exclusion (except for _id)
// This is an error:
// db.products.find({}, { name: 1, specs: 0 })  // ❌

// Project a specific array element by index
db.products.find({}, { "tags": { $slice: 1 } })  // first element only

// $elemMatch in projection — return only the first matching array element
db.orders.find(
  { "items.productId": ObjectId("prod_001") },
  { "items.$": 1 }
)

Sorting, Limiting, and Skipping

javascript
// Sort ascending (1) or descending (-1)
db.products.find().sort({ price: 1 })
db.products.find().sort({ createdAt: -1 })

// Compound sort
db.products.find().sort({ brand: 1, price: -1 })

// Limit the result set
db.products.find().sort({ price: 1 }).limit(10)

// Offset-based pagination (use only for small collections)
const page = 3
const pageSize = 20
db.products.find().sort({ _id: 1 }).skip((page - 1) * pageSize).limit(pageSize)

// Cursor-based pagination (efficient for large collections)
const lastId = ObjectId("last_seen_id")
db.products.find({ _id: { $gt: lastId } }).sort({ _id: 1 }).limit(20)

Cursor Methods

javascript
// Count without fetching documents
db.products.countDocuments({ brand: "Logitech" })

// Estimated count (uses metadata — very fast, not exact after unclean shutdown)
db.products.estimatedDocumentCount()

// Check if any matching document exists
db.products.findOne({ price: { $lt: 10 } }) !== null

// Iterate a cursor
const cursor = db.products.find({ tags: "electronics" })
while (cursor.hasNext()) {
  const doc = cursor.next()
  print(doc.name, doc.price)
}

// Convert cursor to array (use only when result set is bounded)
const results = db.products.find({ stock: { $gt: 0 } }).toArray()

// forEach
db.products.find({ stock: 0 }).forEach(doc => {
  print("Out of stock:", doc.name)
})

Distinct Values and Aggregation Shortcuts

javascript
// Get all unique values for a field
db.products.distinct("brand")

// Distinct values matching a filter
db.products.distinct("brand", { price: { $lt: 100 } })