Запросы MongoDB в Python: фильтры, операторы и проекция
Научитесь делать запросы к MongoDB на Python с pymongo — фильтрация документов, операторы сравнения и логические операторы, проекция полей и пагинация.
В этой главе объясняется, как строить запросы в MongoDB с помощью драйвера Python pymongo. Вы научитесь фильтровать документы по точному совпадению и с операторами сравнения, объединять условия с логическими операторами, выбирать только нужные поля с помощью проекции и разбивать результаты на страницы с skip() и limit().
Если вы ещё не настроили подключение, сначала ознакомьтесь с MongoDB Get Started и MongoDB Create Collection.
Подготовка тестовых данных
Все примеры в этой главе используют одну и ту же коллекцию customers. Выполните этот фрагмент один раз, чтобы заполнить её данными:
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.")Получение всех документов
Вызов find() с пустым фильтром ({}) возвращает все документы из коллекции:
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}
# ...Используйте find_one(), если вам нужен только первый подходящий документ:
doc = col.find_one({"city": "London"})
print(doc)
# {'_id': ..., 'name': 'Alice', 'age': 30, 'city': 'London', 'score': 88}find_one() возвращает None, если ни один документ не найден — всегда проверяйте это перед обращением к полям.
Фильтрация по точному совпадению
Передайте словарь в find(), чтобы найти документы, в которых поле равно определённому значению:
# All customers in London
results = col.find({"city": "London"})
for doc in results:
print(doc["name"], doc["city"])
# Alice London
# Carol LondonНесколько ключей в одном словаре-фильтре работают как неявный AND — оба условия должны выполняться:
# 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Операторы сравнения
Операторы сравнения MongoDB позволяют находить документы, в которых значение поля попадает в указанный диапазон или набор. Все операторы начинаются с $.
| Оператор | Значение | Пример фильтра |
|---|---|---|
$eq | Равно | {"age": {"$eq": 30}} |
$ne | Не равно | {"city": {"$ne": "London"}} |
$gt | Больше | {"score": {"$gt": 80}} |
$gte | Больше или равно | {"score": {"$gte": 80}} |
$lt | Меньше | {"age": {"$lt": 28}} |
$lte | Меньше или равно | {"age": {"$lte": 28}} |
$in | В списке | {"city": {"$in": ["London", "Berlin"]}} |
$nin | Не в списке | {"city": {"$nin": ["London"]}} |
Пример — клиенты с баллом выше 80:
results = col.find({"score": {"$gt": 80}})
for doc in results:
print(doc["name"], doc["score"])
# Alice 88
# Carol 91Пример — клиенты в возрасте от 25 до 30 лет включительно:
results = col.find({"age": {"$gte": 25, "$lte": 30}})
for doc in results:
print(doc["name"], doc["age"])
# Alice 30
# Bob 25
# Dave 28Как показано выше, в одном словаре можно комбинировать несколько операторов для одного поля.
Пример — клиенты из Лондона или Берлина:
results = col.find({"city": {"$in": ["London", "Berlin"]}})
for doc in results:
print(doc["name"], doc["city"])
# Alice London
# Carol London
# Dave BerlinЛогические операторы
Используйте $and, $or и $nor, когда нужно объединить условия так, как это невозможно выразить через обычные ключи словаря.
$or — должно выполняться хотя бы одно условие
# 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 — применяется при двух разных операторах к одному полю
Неявный AND (несколько ключей) не работает, когда нужно применить два $-оператора к одному и тому же полю. В этом случае используйте $and:
# 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 — ни одно из условий не должно выполняться
# 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 — отрицание выражения для одного поля
$not оборачивает выражение с оператором для одного поля и возвращает документы, для которых условие ложно или поле отсутствует:
# 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Фильтрация с регулярными выражениями
Используйте оператор $regex (или передайте скомпилированный шаблон re) для поиска подстроки или сопоставления по шаблону в строковых полях:
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" делает поиск регистронезависимым. Избегайте шаблонов с ведущим символом подстановки вида .*text на больших коллекциях — они не могут использовать индекс и приводят к полному сканированию коллекции.
Проекция — возврат только нужных полей
По умолчанию find() возвращает все поля документа, включая _id. Используйте второй аргумент (проекцию), чтобы включать или исключать поля.
Включение конкретных полей
Передайте 1 для каждого нужного поля. Будут возвращены только эти поля (и _id):
# 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}
# ...Исключение конкретных полей
Передайте 0 для каждого поля, которое нужно скрыть. Все остальные поля будут возвращены:
# 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}
# ...Нельзя смешивать 1 и 0 в одной проекции (за исключением _id, которое всегда можно установить в 0 совместно с включёнными полями).
Сортировка результатов
Примените .sort() к курсору для упорядочивания результатов. Используйте pymongo.ASCENDING (1) или 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Передайте список кортежей (поле, направление) для сортировки по нескольким полям:
# 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Смотрите MongoDB Sort для более детального изучения параметров сортировки.
Ограничение и пагинация результатов
limit()
limit(n) останавливает курсор после возврата n документов:
# 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() для пагинации
Комбинируйте skip() и limit() для реализации простой постраничной навигации:
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Для больших коллекций предпочтительна пагинация на основе диапазонов — фильтрация по последнему просмотренному _id или временной метке — поскольку skip() внутренне всё равно сканирует пропущенные документы.
Подробнее смотрите в MongoDB Limit.
Подсчёт документов
Используйте count_documents() с тем же словарём-фильтром для подсчёта без получения данных:
total = col.count_documents({})
london = col.count_documents({"city": "London"})
print(f"Total: {total}, In London: {london}")
# Total: 5, In London: 2Избегайте устаревшего cursor.count() — он был удалён в PyMongo 4.
Распространённые ошибки
Курсоры PyMongo исчерпываются после итерации. После прохода по курсору он становится пустым. Сохраните результаты в список, если нужно выполнить итерацию несколько раз:
docs = list(col.find({"city": "London"}))
print(len(docs)) # 2
print(docs[0]["name"]) # Alice_id — это ObjectId, а не string. Если сохранить _id как string и затем попытаться найти документ по нему, запрос вернёт пустой результат. Импортируйте ObjectId из bson для преобразования:
from bson import ObjectId
doc = col.find_one({"_id": ObjectId("665000000000000000000001")})Запросы по умолчанию чувствительны к регистру. {"city": "london"} не найдёт документы, в которых хранится "London". Используйте $regex с $options: "i" для поиска без учёта регистра или нормализуйте значения при вставке.
Следующие шаги
- MongoDB Find —
find()иfind_one()подробно - MongoDB Sort — многопольная сортировка и подсказки индекса
- MongoDB Limit — управление размером результата
- MongoDB Update — изменение документов после их нахождения
- MongoDB Delete — удаление документов из коллекции