W3docs

Обновление документов в MongoDB

Узнайте, как обновлять документы MongoDB в Python с помощью update_one(), update_many(), upsert и операторов $set, $inc, $push.

PyMongo предоставляет два основных метода для изменения документов в коллекции: update_one() изменяет первый документ, соответствующий фильтру, а update_many() изменяет каждый подходящий документ. Оба метода используют операторы обновления MongoDB — инструкции вида $set, $inc и $push — чтобы точно описать, что именно должно измениться, оставляя остальную часть документа нетронутой.

Эта глава охватывает:

  • Подключение к MongoDB с помощью PyMongo
  • update_one() — изменение одного документа
  • update_many() — изменение нескольких документов одновременно
  • Операторы обновления: $set, $unset, $inc, $push, $pull
  • Обновление вложенных полей с помощью точечной нотации
  • Upsert — вставка или обновление за один шаг
  • Проверка результатов с помощью объекта результата

Если вы ещё не вставляли документы, сначала прочитайте MongoDB Insert. Для получения информации о синтаксисе фильтрации см. MongoDB Query.

Подключение к MongoDB

Установите драйвер, если вы ещё этого не сделали:

pip install pymongo

Затем подключитесь к базе данных и коллекции:

import pymongo

client = pymongo.MongoClient("mongodb://localhost:27017/")
db = client["mydatabase"]
mycol = db["mycollection"]

MongoClient использует ленивое подключение — сокет открывается только при первой операции. База данных и коллекция создаются автоматически при первой записи документа в них.

Обновление одного документа

update_one(filter, update) находит первый документ, соответствующий filter, и применяет к нему update.

result = mycol.update_one(
    {"name": "Alice"},       # filter: which document to change
    {"$set": {"age": 31}}    # update: what to change
)

print(result.matched_count)   # 1 if a document was found
print(result.modified_count)  # 1 if a value actually changed

Оператор $set перезаписывает указанные поля и оставляет все остальные поля документа в неизменном виде. Без $set второй аргумент заменит весь документ целиком, что редко бывает нужным.

Почему matched_count и modified_count могут различаться

Если у документа уже есть age: 31, MongoDB находит его, но ничего не записывает. В этом случае matched_count равен 1, а modified_count равен 0. Всегда проверяйте modified_count, чтобы убедиться, что реальная запись произошла.

Обновление нескольких документов

update_many(filter, update) применяет одно и то же обновление ко всем документам, соответствующим фильтру.

result = mycol.update_many(
    {"status": {"$ne": "closed"}},   # every document where status != "closed"
    {"$set": {"status": "open"}}
)

print(f"Matched:  {result.matched_count}")
print(f"Modified: {result.modified_count}")

update_many() атомарен на уровне каждого документа, но не на уровне всей операции. Каждый отдельный документ изменяется атомарно, однако другие клиенты могут читать или писать между обновлениями отдельных документов в рамках одного вызова update_many().

Операторы обновления

MongoDB предоставляет набор операторов обновления, позволяющих точно выразить изменения без передачи всего документа по сети.

$set — установка значений полей

# Set one field
mycol.update_one({"name": "Alice"}, {"$set": {"city": "Boston"}})

# Set multiple fields at once
mycol.update_one({"name": "Alice"}, {"$set": {"city": "Boston", "active": True}})

$unset — удаление поля

# Remove the "temporary" field from the matching document
mycol.update_one({"name": "Alice"}, {"$unset": {"temporary": ""}})

Значение, которое вы присваиваете полю внутри $unset, не имеет значения — MongoDB его игнорирует. По соглашению используется пустая string.

$inc — увеличение или уменьшение числа

# Add 5 to the "score" field (creates it at 5 if it does not exist)
mycol.update_one({"name": "Alice"}, {"$inc": {"score": 5}})

# Subtract 1
mycol.update_one({"name": "Alice"}, {"$inc": {"score": -1}})

$inc безопасен для счётчиков, поскольку является единственной атомарной операцией — два одновременных увеличения будут применены корректно.

$push — добавление элемента в array

# Add a tag to the "tags" array (creates the array if it does not exist)
mycol.update_one({"name": "Alice"}, {"$push": {"tags": "python"}})

$pull — удаление элементов из array

# Remove all occurrences of "python" from the "tags" array
mycol.update_one({"name": "Alice"}, {"$pull": {"tags": "python"}})

Обновление вложенных документов

Используйте точечную нотацию, чтобы обращаться к полям во вложенных документах без перезаписи всего вложенного object.

Рассмотрим документ следующей структуры:

{
  "name": "Alice",
  "address": {
    "city": "New York",
    "state": "NY"
  }
}

Чтобы изменить только поле state внутри address:

mycol.update_one(
    {"name": "Alice"},
    {"$set": {"address.state": "NJ"}}
)

Поле city внутри address остаётся нетронутым. Вы можете углубляться на столько уровней, сколько требует ваша схема — например, "address.geo.lat".

Upsert: вставка или обновление за один шаг

Upsert говорит MongoDB: «обнови этот документ, если он существует; вставь его, если не существует». Передайте upsert=True в качестве именованного аргумента:

result = mycol.update_one(
    {"name": "Bob"},
    {"$set": {"age": 25, "status": "new"}},
    upsert=True
)

if result.upserted_id:
    print(f"Inserted new document with _id: {result.upserted_id}")
else:
    print("Updated an existing document")

Когда MongoDB вставляет новый документ через upsert, атрибут upserted_id объекта результата содержит новый _id. Когда обновляется существующий документ, upserted_id равен None.

update_many() также принимает upsert=True. Если ни один документ не совпадает, MongoDB вставляет один новый документ — при одном upsert-запросе никогда не вставляется несколько документов.

Изучение объекта результата

И update_one(), и update_many() возвращают объект UpdateResult с тремя полезными атрибутами:

АтрибутОписание
matched_countКоличество документов, соответствующих фильтру
modified_countКоличество документов, которые были фактически изменены
upserted_id_id вновь вставленного документа (только при upsert); иначе None
result = mycol.update_many(
    {"role": "guest"},
    {"$set": {"role": "member"}}
)

print(f"Matched:  {result.matched_count}")
print(f"Modified: {result.modified_count}")
print(f"Upserted: {result.upserted_id}")

Распространённые ошибки

Забытый оператор обновления заменяет документ целиком. Этот вызов удаляет все поля, кроме _id, и заменяет документ только значением age: 31:

# Dangerous — replaces the whole document
mycol.update_one({"name": "Alice"}, {"age": 31})

Всегда оборачивайте изменения в оператор ($set, $inc и т.д.).

Фильтрация по _id требует объекта ObjectId. Идентификаторы документов хранятся как объекты ObjectId, а не обычные string:

from bson.objectid import ObjectId

mycol.update_one(
    {"_id": ObjectId("64a1f2c3d4e5f6a7b8c9d0e1")},
    {"$set": {"verified": True}}
)

Передача ID в виде обычной string ничего не найдёт и молча ничего не сделает.

Дальнейшие шаги

  • MongoDB Delete — удаление документов с помощью delete_one() и delete_many()
  • MongoDB Find — получение документов и фильтрация результатов
  • MongoDB Query — расширенные операторы и шаблоны запросов
Was this page helpful?