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 LondonMultiple 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 35Comparison Operators
MongoDB comparison operators let you match documents where a field falls within a range or set. All operators are prefixed with $.
| Operator | Meaning | Example filter |
|---|---|---|
$eq | Equal to | {"age": {"$eq": 30}} |
$ne | Not equal to | {"city": {"$ne": "London"}} |
$gt | Greater than | {"score": {"$gt": 80}} |
$gte | Greater or equal | {"score": {"$gte": 80}} |
$lt | Less than | {"age": {"$lt": 28}} |
$lte | Less or equal | {"age": {"$lte": 28}} |
$in | In a list | {"city": {"$in": ["London", "Berlin"]}} |
$nin | Not 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 91Example — 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 28You 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 BerlinLogical 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 77Regular 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 65Pass 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 22See 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 77skip() 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 74For 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: 2Avoid 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
- MongoDB Find —
find()andfind_one()in depth - MongoDB Sort — multi-field sorting and index hints
- MongoDB Limit — controlling result size
- MongoDB Update — modifying documents after finding them
- MongoDB Delete — removing documents from a collection