W3docs

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 pymongo

All 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 34

Filter 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
# Carol

Projections — 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 1 to include a field, 0 to exclude it.
  • You cannot mix inclusion and exclusion in the same projection, except for _id (which can always be explicitly set to 0).

Query Operators

MongoDB provides a rich set of operators for filtering documents. Pass them inside the filter dictionary.

Comparison Operators

OperatorMeaningExample
$eqEqual (default){"age": {"$eq": 34}}
$neNot equal{"city": {"$ne": "Paris"}}
$gtGreater than{"age": {"$gt": 30}}
$gteGreater than or equal{"age": {"$gte": 34}}
$ltLess than{"age": {"$lt": 30}}
$lteLess than or equal{"age": {"$lte": 28}}
$inValue in list{"city": {"$in": ["London", "Berlin"]}}
$ninValue 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 (or 1) — A → Z, smallest to largest
  • pymongo.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)  # 5

Avoid 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)  # True

Common 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 like

find_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.

Was this page helpful?