W3docs

MongoDB Query in Python: Filters, Operators & Projection

Learn how to query MongoDB with Python and pymongo — filter documents, use comparison and logical operators, project fields, and paginate results.

This chapter explains how to build queries in MongoDB using Python's pymongo driver. You will learn to filter documents with exact matches and comparison operators, combine conditions with logical operators, select only the fields you need with projection, and paginate results with skip() and limit().

If you have not set up a connection yet, see MongoDB Get Started and MongoDB Create Collection first.

Sample Data Setup

All examples in this chapter use the same customers collection. Run this snippet once to populate it:

import pymongo

client = pymongo.MongoClient("mongodb://localhost:27017/")
db = client["mystore"]
col = db["customers"]

# Drop and re-create so examples are repeatable
col.drop()
col.insert_many([
    {"name": "Alice", "age": 30, "city": "London",   "score": 88},
    {"name": "Bob",   "age": 25, "city": "New York",  "score": 74},
    {"name": "Carol", "age": 35, "city": "London",   "score": 91},
    {"name": "Dave",  "age": 28, "city": "Berlin",    "score": 65},
    {"name": "Eve",   "age": 22, "city": "New York",  "score": 77},
])
print("Sample data inserted.")

Querying All Documents

Calling find() with an empty filter ({}) returns every document in the collection:

for doc in col.find({}):
    print(doc)
# {'_id': ..., 'name': 'Alice', 'age': 30, 'city': 'London',   'score': 88}
# {'_id': ..., 'name': 'Bob',   'age': 25, 'city': 'New York',  'score': 74}
# ...

Use find_one() when you only need the first matching document:

doc = col.find_one({"city": "London"})
print(doc)
# {'_id': ..., 'name': 'Alice', 'age': 30, 'city': 'London', 'score': 88}

find_one() returns None if no document matches — always check for that before accessing fields.

Exact-Match Filters

Pass a dictionary to find() to match documents where a field equals a specific value:

# All customers in London
results = col.find({"city": "London"})
for doc in results:
    print(doc["name"], doc["city"])
# Alice London
# Carol London

Multiple keys in the same filter dictionary act as an implicit AND — both conditions must be true:

# Customers in London AND older than 30
results = col.find({"city": "London", "age": {"$gt": 30}})
for doc in results:
    print(doc["name"], doc["age"])
# Carol 35

Comparison Operators

MongoDB comparison operators let you match documents where a field falls within a range or set. All operators are prefixed with $.

OperatorMeaningExample filter
$eqEqual to{"age": {"$eq": 30}}
$neNot equal to{"city": {"$ne": "London"}}
$gtGreater than{"score": {"$gt": 80}}
$gteGreater or equal{"score": {"$gte": 80}}
$ltLess than{"age": {"$lt": 28}}
$lteLess or equal{"age": {"$lte": 28}}
$inIn a list{"city": {"$in": ["London", "Berlin"]}}
$ninNot in a list{"city": {"$nin": ["London"]}}

Example — customers with a score above 80:

results = col.find({"score": {"$gt": 80}})
for doc in results:
    print(doc["name"], doc["score"])
# Alice 88
# Carol 91

Example — customers aged 25 to 30 (inclusive):

results = col.find({"age": {"$gte": 25, "$lte": 30}})
for doc in results:
    print(doc["name"], doc["age"])
# Alice 30
# Bob   25
# Dave  28

You can stack multiple operators on the same field inside a single dictionary, as shown above.

Example — customers in London or Berlin:

results = col.find({"city": {"$in": ["London", "Berlin"]}})
for doc in results:
    print(doc["name"], doc["city"])
# Alice London
# Carol London
# Dave  Berlin

Logical Operators

Use $and, $or, and $nor when you need to combine conditions in ways that plain dictionary keys cannot express.

$or — at least one condition must match

# Customers younger than 25 OR with a score above 90
results = col.find({"$or": [{"age": {"$lt": 25}}, {"score": {"$gt": 90}}]})
for doc in results:
    print(doc["name"], doc["age"], doc["score"])
# Carol 35 91
# Eve   22 77

$and — use when applying two different operators to the same field

The implicit AND (multiple keys) does not work when you need two $ operators on the same field. Use $and instead:

# Customers whose score is > 65 AND < 90
# (cannot use {"score": {"$gt": 65}, "score": {"$lt": 90}} — duplicate key)
results = col.find({"$and": [{"score": {"$gt": 65}}, {"score": {"$lt": 90}}]})
for doc in results:
    print(doc["name"], doc["score"])
# Alice 88
# Bob   74
# Eve   77

$nor — none of the conditions must match

# Customers who are NOT in London AND do NOT have score > 80
results = col.find({"$nor": [{"city": "London"}, {"score": {"$gt": 80}}]})
for doc in results:
    print(doc["name"], doc["city"], doc["score"])
# Bob  New York 74
# Dave Berlin   65
# Eve  New York 77

$not — negate a single field expression

$not wraps a single operator expression and returns documents where the condition is false or the field does not exist:

# Customers who do NOT have a score greater than 80
results = col.find({"score": {"$not": {"$gt": 80}}})
for doc in results:
    print(doc["name"], doc["score"])
# Bob  74
# Dave 65
# Eve  77

Regular Expression Filters

Use the $regex operator (or pass a compiled re pattern) to do substring or pattern matching on string fields:

import re

# Customers whose name starts with a vowel
results = col.find({"name": {"$regex": "^[AEIOU]", "$options": "i"}})
for doc in results:
    print(doc["name"])
# Alice
# Eve

$options: "i" makes the match case-insensitive. Avoid leading-wildcard patterns like .*text on large collections — they cannot use an index and will perform a full collection scan.

Projection — Returning Only Specific Fields

By default, find() returns every field in the document, including _id. Use a second argument (the projection) to include or exclude fields.

Include specific fields

Pass 1 for each field you want. Only those fields (plus _id) are returned:

# Return only name and score
results = col.find({}, {"name": 1, "score": 1})
for doc in results:
    print(doc)
# {'_id': ..., 'name': 'Alice', 'score': 88}
# {'_id': ..., 'name': 'Bob',   'score': 74}
# ...

Exclude specific fields

Pass 0 for each field you want to hide. All other fields are returned:

# Hide _id and city
results = col.find({}, {"_id": 0, "city": 0})
for doc in results:
    print(doc)
# {'name': 'Alice', 'age': 30, 'score': 88}
# {'name': 'Bob',   'age': 25, 'score': 74}
# ...

You cannot mix 1 and 0 in the same projection (except for _id, which can always be set to 0 alongside inclusions).

Sorting Results

Chain .sort() on the cursor to order results. Use pymongo.ASCENDING (1) or pymongo.DESCENDING (-1):

import pymongo

# Sort by score descending
results = col.find({}, {"_id": 0, "name": 1, "score": 1}).sort("score", pymongo.DESCENDING)
for doc in results:
    print(doc["name"], doc["score"])
# Carol 91
# Alice 88
# Eve   77
# Bob   74
# Dave  65

Pass a list of (field, direction) tuples to sort by multiple fields:

# Sort by city ascending, then by age descending within each city
results = col.find({}, {"_id": 0, "name": 1, "city": 1, "age": 1}).sort(
    [("city", pymongo.ASCENDING), ("age", pymongo.DESCENDING)]
)
for doc in results:
    print(doc["city"], doc["name"], doc["age"])
# Berlin  Dave  28
# London  Carol 35
# London  Alice 30
# New York Bob   25   <- Bob (25) vs Eve (22): descending so 25 first
# New York Eve   22

See MongoDB Sort for a deeper look at sorting options.

Limiting and Paginating Results

limit()

limit(n) stops the cursor after returning n documents:

# Top 3 by score
results = col.find({}, {"_id": 0, "name": 1, "score": 1}).sort("score", -1).limit(3)
for doc in results:
    print(doc["name"], doc["score"])
# Carol 91
# Alice 88
# Eve   77

skip() for pagination

Combine skip() and limit() to implement simple page-by-page navigation:

page_size = 2
page = 1  # zero-indexed

results = (
    col.find({}, {"_id": 0, "name": 1, "score": 1})
       .sort("score", -1)
       .skip(page * page_size)
       .limit(page_size)
)
for doc in results:
    print(doc["name"], doc["score"])
# Eve  77   (page 1, items 3–4 of the sorted list)
# Bob  74

For large collections, prefer range-based pagination — filter on the last seen _id or a timestamp — because skip() still scans over skipped documents internally.

See MongoDB Limit for more detail.

Counting Documents

Use count_documents() with the same filter dict to count without fetching data:

total = col.count_documents({})
london = col.count_documents({"city": "London"})
print(f"Total: {total}, In London: {london}")
# Total: 5, In London: 2

Avoid the deprecated cursor.count() — it was removed in PyMongo 4.

Common Gotchas

PyMongo cursors are exhausted after iteration. Once you loop over a cursor, it is empty. Store results in a list if you need to iterate more than once:

docs = list(col.find({"city": "London"}))
print(len(docs))   # 2
print(docs[0]["name"])  # Alice

_id is an ObjectId, not a string. If you store an _id as a string and later try to query by it, the query will return nothing. Import ObjectId from bson to convert:

from bson import ObjectId

doc = col.find_one({"_id": ObjectId("665000000000000000000001")})

Queries are case-sensitive by default. {"city": "london"} will not match documents stored as "London". Use $regex with $options: "i" for case-insensitive matching, or normalize values at insert time.

Next Steps

Was this page helpful?