W3docs

Запросы 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   77

skip() для пагинации

Комбинируйте 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 Findfind() и find_one() подробно
  • MongoDB Sort — многопольная сортировка и подсказки индекса
  • MongoDB Limit — управление размером результата
  • MongoDB Update — изменение документов после их нахождения
  • MongoDB Delete — удаление документов из коллекции
Was this page helpful?