MongoDB Find
Learn how to retrieve MongoDB documents with Python using find_one(), find(), projections, query operators, sort, skip, and limit with PyMongo.
This chapter explains how to retrieve documents from a MongoDB collection using Python's pymongo driver. You will learn the find_one() and find() methods, how to filter results with query operators, how to control which fields are returned with projections, and how to sort, skip, and limit results.
Setting Up
Make sure pymongo is installed before running any example:
pip install pymongoAll examples below assume a live MongoDB server at mongodb://localhost:27017/. To follow along on your own machine, start MongoDB with mongod or use a free cloud cluster (MongoDB Atlas).
Preparing Sample Data
The examples in this chapter use a customers collection containing these five documents. Run this once to populate it:
import pymongo
client = pymongo.MongoClient("mongodb://localhost:27017/")
db = client["mydatabase"]
col = db["customers"]
# Insert sample documents (skip if already inserted)
col.drop() # start fresh
col.insert_many([
{"name": "Alice", "age": 28, "city": "London"},
{"name": "Bob", "age": 34, "city": "Paris"},
{"name": "Carol", "age": 22, "city": "London"},
{"name": "David", "age": 40, "city": "Berlin"},
{"name": "Eve", "age": 34, "city": "Paris"},
])
print("Sample data ready.")Expected output:
Sample data ready.PyMongo automatically adds a unique _id field (a bson.ObjectId) to every document that does not already have one.
Retrieving a Single Document with find_one()
find_one() returns the first document that matches the filter, or None if no document matches. It is the right choice when you expect exactly one result (for example, looking up a user by email).
# Retrieve the first document in the collection
doc = col.find_one()
print(doc)
# {'_id': ObjectId('...'), 'name': 'Alice', 'age': 28, 'city': 'London'}Pass a filter to match a specific document:
# Find the customer named Bob
bob = col.find_one({"name": "Bob"})
print(bob)
# {'_id': ObjectId('...'), 'name': 'Bob', 'age': 34, 'city': 'Paris'}If no document matches, find_one() returns None, so always guard against it:
result = col.find_one({"name": "Zara"})
if result is None:
print("No document found.")Retrieving Multiple Documents with find()
find() returns a cursor — a lazy iterator over all matching documents. Nothing is fetched from the server until you iterate.
Retrieve All Documents
# Iterate every document in the collection
for doc in col.find():
print(doc["name"], doc["age"])Expected output (order may vary without an explicit sort):
Alice 28
Bob 34
Carol 22
David 40
Eve 34Filter with an Exact Match
Pass a dictionary as the first argument to find():
# All customers in London
for doc in col.find({"city": "London"}):
print(doc["name"])
# Alice
# CarolProjections — Choosing Which Fields to Return
By default, MongoDB returns every field including _id. A projection lets you include or exclude specific fields, which reduces network traffic and memory usage.
Pass the projection as the second positional argument (or keyword argument projection):
# Return only name and city; suppress _id
for doc in col.find({}, {"_id": 0, "name": 1, "city": 1}):
print(doc)
# {'name': 'Alice', 'city': 'London'}
# {'name': 'Bob', 'city': 'Paris'}
# ...Rules for projections:
- Use
1to include a field,0to exclude it. - You cannot mix inclusion and exclusion in the same projection, except for
_id(which can always be explicitly set to0).
Query Operators
MongoDB provides a rich set of operators for filtering documents. Pass them inside the filter dictionary.
Comparison Operators
| Operator | Meaning | Example |
|---|---|---|
$eq | Equal (default) | {"age": {"$eq": 34}} |
$ne | Not equal | {"city": {"$ne": "Paris"}} |
$gt | Greater than | {"age": {"$gt": 30}} |
$gte | Greater than or equal | {"age": {"$gte": 34}} |
$lt | Less than | {"age": {"$lt": 30}} |
$lte | Less than or equal | {"age": {"$lte": 28}} |
$in | Value in list | {"city": {"$in": ["London", "Berlin"]}} |
$nin | Value not in list | {"city": {"$nin": ["Paris"]}} |
Example — customers older than 30:
for doc in col.find({"age": {"$gt": 30}}, {"_id": 0, "name": 1, "age": 1}):
print(doc)
# {'name': 'Bob', 'age': 34}
# {'name': 'David', 'age': 40}
# {'name': 'Eve', 'age': 34}Example — customers in London or Berlin:
for doc in col.find(
{"city": {"$in": ["London", "Berlin"]}},
{"_id": 0, "name": 1, "city": 1}
):
print(doc)
# {'name': 'Alice', 'city': 'London'}
# {'name': 'Carol', 'city': 'London'}
# {'name': 'David', 'city': 'Berlin'}Logical Operators
Implicit AND — providing multiple keys in a single filter dictionary means all conditions must match:
# Age > 30 AND city is Paris
for doc in col.find({"age": {"$gt": 30}, "city": "Paris"}, {"_id": 0}):
print(doc)
# {'name': 'Bob', 'age': 34, 'city': 'Paris'}
# {'name': 'Eve', 'age': 34, 'city': 'Paris'}$and is required when you need to apply two different conditions to the same field:
# Age between 28 (inclusive) and 40 (exclusive)
query = {"$and": [{"age": {"$gte": 28}}, {"age": {"$lt": 40}}]}
for doc in col.find(query, {"_id": 0, "name": 1, "age": 1}):
print(doc)
# {'name': 'Alice', 'age': 28}
# {'name': 'Bob', 'age': 34}
# {'name': 'Eve', 'age': 34}$or — at least one condition must match:
# City is Berlin OR age is 22
for doc in col.find(
{"$or": [{"city": "Berlin"}, {"age": 22}]},
{"_id": 0, "name": 1}
):
print(doc)
# {'name': 'Carol'}
# {'name': 'David'}Pattern Matching with $regex
Use $regex to match string fields against a regular expression:
# Names that start with the letter 'C' or 'E' (case-sensitive)
for doc in col.find({"name": {"$regex": "^[CE]"}}, {"_id": 0, "name": 1}):
print(doc)
# {'name': 'Carol'}
# {'name': 'Eve'}For case-insensitive matching, add $options: "i":
for doc in col.find(
{"city": {"$regex": "london", "$options": "i"}},
{"_id": 0, "name": 1, "city": 1}
):
print(doc)
# {'name': 'Alice', 'city': 'London'}
# {'name': 'Carol', 'city': 'London'}Sorting Results
Chain .sort() on the cursor. Pass the field name and a direction constant:
pymongo.ASCENDING(or1) — A → Z, smallest to largestpymongo.DESCENDING(or-1) — Z → A, largest to smallest
# Sort by age ascending
for doc in col.find({}, {"_id": 0, "name": 1, "age": 1}).sort("age", pymongo.ASCENDING):
print(doc)
# {'name': 'Carol', 'age': 22}
# {'name': 'Alice', 'age': 28}
# {'name': 'Bob', 'age': 34}
# {'name': 'Eve', 'age': 34}
# {'name': 'David', 'age': 40}Sort by multiple fields by passing a list of (field, direction) tuples:
# Sort by age descending, then by name ascending (tiebreak)
order = [("age", pymongo.DESCENDING), ("name", pymongo.ASCENDING)]
for doc in col.find({}, {"_id": 0, "name": 1, "age": 1}).sort(order):
print(doc)
# {'name': 'David', 'age': 40}
# {'name': 'Bob', 'age': 34}
# {'name': 'Eve', 'age': 34}
# {'name': 'Alice', 'age': 28}
# {'name': 'Carol', 'age': 22}Limiting Results
.limit(n) caps the number of documents returned. This is useful for showing the top N results.
# Top 3 youngest customers
for doc in col.find({}, {"_id": 0, "name": 1, "age": 1}).sort("age", 1).limit(3):
print(doc)
# {'name': 'Carol', 'age': 22}
# {'name': 'Alice', 'age': 28}
# {'name': 'Bob', 'age': 34}Skipping Documents (Pagination)
.skip(n) skips the first n documents. Combined with .limit(), it enables page-based pagination:
PAGE_SIZE = 2
def get_page(page_number):
"""Return one page of customers sorted by age (page_number is 0-indexed)."""
return list(
col.find({}, {"_id": 0, "name": 1, "age": 1})
.sort("age", pymongo.ASCENDING)
.skip(page_number * PAGE_SIZE)
.limit(PAGE_SIZE)
)
print(get_page(0)) # [{'name': 'Carol', 'age': 22}, {'name': 'Alice', 'age': 28}]
print(get_page(1)) # [{'name': 'Bob', 'age': 34}, {'name': 'Eve', 'age': 34}]
print(get_page(2)) # [{'name': 'David', 'age': 40}]For large collections, prefer cursor-based pagination (filter by the last seen _id) over skip(), because skip() must scan and discard documents, which gets slow as the offset grows.
Counting Matching Documents
Use count_documents() with a filter to count matches without fetching the documents:
london_count = col.count_documents({"city": "London"})
print(london_count) # 2
total = col.count_documents({})
print(total) # 5Avoid the older .count() method on cursors — it was deprecated in PyMongo 3.7 and removed in PyMongo 4.
Checking Whether a Document Exists
When you only need to know whether at least one document matches, use find_one() (cheaper than counting):
exists = col.find_one({"city": "Berlin"}) is not None
print(exists) # TrueCommon Gotchas
The cursor is exhausted after one iteration. If you iterate the same cursor twice, the second loop produces nothing. Call find() again or convert to a list:
cursor = col.find({"city": "Paris"})
results = list(cursor) # materialise once
print(len(results)) # 2
# Now you can iterate `results` as many times as you likefind_one() vs find() — pick the right one. If you know there is at most one match (for example, querying by a unique field such as email), use find_one(). Using find() forces you to iterate even when you only need one result.
None filter vs empty dict. Both find() and find({}) return all documents. Avoid passing None explicitly — use {} for clarity.
Related Chapters
- MongoDB Insert — insert documents into a collection before querying them
- MongoDB Query — deeper coverage of query expressions and filtering patterns
- MongoDB Sort — dedicated coverage of multi-field sorting
- MongoDB Limit — limit and its interaction with indexes
- MongoDB Update — modify documents that you have found
- MongoDB Delete — remove matching documents