MongoDB Find
Узнайте, как извлекать документы MongoDB с Python: find_one(), find(), проекции, операторы запросов, сортировка, skip и limit через PyMongo.
В этой главе объясняется, как извлекать документы из коллекции MongoDB с помощью драйвера pymongo для Python. Вы узнаете про методы find_one() и find(), как фильтровать результаты с помощью операторов запросов, как управлять возвращаемыми полями с помощью проекций, а также как сортировать, пропускать и ограничивать результаты.
Настройка
Убедитесь, что pymongo установлен перед запуском любого примера:
pip install pymongoВсе приведённые ниже примеры предполагают наличие работающего сервера MongoDB по адресу mongodb://localhost:27017/. Чтобы воспроизвести примеры на своём компьютере, запустите MongoDB командой mongod или воспользуйтесь бесплатным облачным кластером (MongoDB Atlas).
Подготовка тестовых данных
В примерах этой главы используется коллекция customers, содержащая пять документов. Выполните следующий код один раз, чтобы заполнить её:
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.")Ожидаемый вывод:
Sample data ready.PyMongo автоматически добавляет уникальное поле _id (типа bson.ObjectId) к каждому документу, у которого его ещё нет.
Получение одного документа с помощью find_one()
find_one() возвращает первый документ, соответствующий фильтру, или None, если совпадений нет. Это правильный выбор, когда вы ожидаете ровно один результат (например, при поиске пользователя по email).
# Retrieve the first document in the collection
doc = col.find_one()
print(doc)
# {'_id': ObjectId('...'), 'name': 'Alice', 'age': 28, 'city': 'London'}Передайте фильтр для поиска конкретного документа:
# Find the customer named Bob
bob = col.find_one({"name": "Bob"})
print(bob)
# {'_id': ObjectId('...'), 'name': 'Bob', 'age': 34, 'city': 'Paris'}Если ни один документ не совпадает, find_one() возвращает None, поэтому всегда проверяйте этот случай:
result = col.find_one({"name": "Zara"})
if result is None:
print("No document found.")Получение нескольких документов с помощью find()
find() возвращает курсор — ленивый итератор по всем совпадающим документам. Данные с сервера не запрашиваются до начала итерации.
Получить все документы
# Iterate every document in the collection
for doc in col.find():
print(doc["name"], doc["age"])Ожидаемый вывод (порядок может отличаться без явной сортировки):
Alice 28
Bob 34
Carol 22
David 40
Eve 34Фильтрация по точному совпадению
Передайте словарь в качестве первого аргумента find():
# All customers in London
for doc in col.find({"city": "London"}):
print(doc["name"])
# Alice
# CarolПроекции — выбор возвращаемых полей
По умолчанию MongoDB возвращает все поля, включая _id. Проекция позволяет включать или исключать конкретные поля, что снижает нагрузку на сеть и потребление памяти.
Передайте проекцию в качестве второго позиционного аргумента (или именованного аргумента 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'}
# ...Правила проекций:
- Используйте
1для включения поля,0— для исключения. - Нельзя смешивать включение и исключение в одной проекции, кроме поля
_id(которое всегда можно явно установить в0).
Операторы запросов
MongoDB предоставляет широкий набор операторов для фильтрации документов. Передавайте их внутри словаря фильтра.
Операторы сравнения
| Оператор | Значение | Пример |
|---|---|---|
$eq | Равно (по умолчанию) | {"age": {"$eq": 34}} |
$ne | Не равно | {"city": {"$ne": "Paris"}} |
$gt | Больше | {"age": {"$gt": 30}} |
$gte | Больше или равно | {"age": {"$gte": 34}} |
$lt | Меньше | {"age": {"$lt": 30}} |
$lte | Меньше или равно | {"age": {"$lte": 28}} |
$in | Значение в списке | {"city": {"$in": ["London", "Berlin"]}} |
$nin | Значение не в списке | {"city": {"$nin": ["Paris"]}} |
Пример — клиенты старше 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}Пример — клиенты из Лондона или Берлина:
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'}Логические операторы
Неявный AND — указание нескольких ключей в одном словаре фильтра означает, что все условия должны выполняться:
# 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 необходим, когда нужно применить два разных условия к одному и тому же полю:
# 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 — должно выполняться хотя бы одно условие:
# 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'}Сопоставление по шаблону с $regex
Используйте $regex для сопоставления строковых полей с регулярным выражением:
# 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'}Для поиска без учёта регистра добавьте $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'}Сортировка результатов
Используйте .sort() на курсоре. Передайте имя поля и константу направления:
pymongo.ASCENDING(или1) — от A до Z, от меньшего к большемуpymongo.DESCENDING(или-1) — от Z до A, от большего к меньшему
# 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 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}Ограничение результатов
.limit(n) ограничивает количество возвращаемых документов. Это удобно для отображения топ-N результатов.
# 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}Пропуск документов (пагинация)
.skip(n) пропускает первые n документов. В сочетании с .limit() это обеспечивает постраничную пагинацию:
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}]Для больших коллекций предпочтительнее использовать курсорную пагинацию (фильтрацию по последнему увиденному _id) вместо skip(), поскольку skip() вынужден сканировать и пропускать документы, что замедляется по мере роста смещения.
Подсчёт совпадающих документов
Используйте count_documents() с фильтром для подсчёта совпадений без загрузки документов:
london_count = col.count_documents({"city": "London"})
print(london_count) # 2
total = col.count_documents({})
print(total) # 5Избегайте устаревшего метода .count() на курсорах — он был помечен устаревшим в PyMongo 3.7 и удалён в PyMongo 4.
Проверка существования документа
Если нужно лишь убедиться, что хотя бы один документ соответствует условию, используйте find_one() (это дешевле, чем подсчёт):
exists = col.find_one({"city": "Berlin"}) is not None
print(exists) # TrueТипичные ошибки
Курсор исчерпывается после одной итерации. Если вы дважды итерируете один и тот же курсор, второй цикл не вернёт ничего. Вызовите find() снова или преобразуйте курсор в список:
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() — выбирайте правильный метод. Если вы знаете, что совпадение может быть не более одного (например, запрос по уникальному полю, такому как email), используйте find_one(). Использование find() заставляет вас итерировать курсор, даже когда нужен лишь один результат.
Фильтр None vs пустой словарь. Как find(), так и find({}) возвращают все документы. Избегайте явной передачи None — для ясности используйте {}.
Связанные главы
- MongoDB Insert — вставка документов в коллекцию перед их запросом
- MongoDB Query — более глубокое рассмотрение выражений запросов и шаблонов фильтрации
- MongoDB Sort — подробное описание многополевой сортировки
- MongoDB Limit — ограничение результатов и его взаимодействие с индексами
- MongoDB Update — изменение найденных документов
- MongoDB Delete — удаление совпадающих документов