Модуль 1 · Подключение и первые данные

Справочник по MongoDB · модуль 1 · подключение и первые данные

Глава 1.7

1.7

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

Третья операция — изменить то, что уже лежит в базе. Разберём операторы $set, $inc и $unset, разница между «нашлось» и «изменилось», изменение пачки документов одним запросом и то, что делает upsert.

Категория
Операции над данными
Уровень
Начальный
Связанные темы
Вставка, удаление, операторы фильтра
Зачем это знать
Менять данные точечно и понимать, что именно изменилось

Как запускать примеры этой главы

БазыГлава меняет песочницу. Перед началом верните базы в исходное состояние: docker compose run --rm reset или скрипт из главы 1.0, §6.
Архивmongodb-glava-1.7-python.zip, 66 КБmongodb-glava-1.7-ruby.zip, 66 КБmongodb-glava-1.7-go.zip, 73 КБmongodb-glava-1.7-cpp.zip, 71 КБ — все примеры главы готовыми программами, базы и стенд в Docker. В README.md — запуск в Docker, если Compass нет или он не подключается, и на установленном сервере с Compass, а также что должна напечатать каждая программа.
Файлlessons/ch1_7.py — пример вставляется в конец файлаlessons/ch1_7.rb — пример вставляется в конец файлаlessons/ch1_7.go — пример внутрь скобок в конце main, функции и типы — выше mainlessons/ch1_7.cpp — пример внутрь скобок в конце main, функции — выше main
Запускcd lessons, затем python ch1_7.pycd lessons, затем python3 ch1_7.py · в Docker: docker compose run --rm python lessons/ch1_7.pycd lessons, затем ruby ch1_7.rb · в Docker: docker compose run --rm ruby lessons/ch1_7.rbcd lessons, затем go run ch1_7.go · в Docker: docker compose run --rm go lessons/ch1_7.gocd lessons, затем g++ -std=c++17 ch1_7.cpp -o prog $(pkg-config --cflags --libs libmongocxx1) && ./prog · в Docker: docker compose run --rm cpp lessons/ch1_7.cpp
ПорядокКаждый пример вставляется вместо предыдущего и запускается один раз, по порядку, — так получены все выводы главы. Подробно — глава 1.0, §10. Заготовка добавляет в песочницу три товара из главы 1.3 и копию заказов sandbox.orders, если их там нет.

§1Два документа: кого и что

У любого обновления два аргумента, и оба — документы:

фильтрКого менять. Тот же самый фильтр, что и в find, — со всеми его правилами.кого
изменениеЧто делать. Документ из операторов обновления: $set, $inc, $unset и других.что

Методов тоже два: update_one меняет первый подходящий документ, update_many — все подходящие. Разница в одном слове, а последствия разные: если фильтр случайно подошёл к сотне документов, update_one испортит один, а update_many — сто.

Работаем, как и в главе о вставке, в песочнице:

Python · PyMongoподключение
box = client["sandbox"]["products"]
C++ · mongocxxподключение
auto box = client["sandbox"]["products"];
Go · mongo-driverподключение
box = client.Database("sandbox").Collection("products")
Ruby · mongoподключение
box = client.use("sandbox").database[:products]

§2$set: задать значение

Оператор $set записывает значение в поле. Если поля не было — оно появится, если было — заменится. Остальные поля документа не тронуты.

Python · PyMongoновая цена одного товара
res = box.update_one(
    {"_id": "p-101"},
    {"$set": {"price": 2690}})

print("matched:", res.matched_count, "modified:", res.modified_count)
C++ · mongocxxновая цена одного товара
auto res = box.update_one(
    make_document(kvp("_id", "p-101")),
    make_document(kvp("$set", make_document(kvp("price", 2690)))));

std::cout << "matched: " << res->matched_count()
          << " modified: " << res->modified_count() << std::endl;
Go · mongo-driverновая цена одного товара
res, _ := box.UpdateOne(ctx,
    bson.D{{Key: "_id", Value: "p-101"}},
    bson.D{{Key: "$set", Value: bson.D{{Key: "price", Value: 2690}}}})

fmt.Println("matched:", res.MatchedCount, "modified:", res.ModifiedCount)
Ruby · mongoновая цена одного товара
res = box.update_one(
  { "_id" => "p-101" },
  { "$set" => { "price" => 2690 } }
)

puts "matched: #{res.matched_count} modified: #{res.modified_count}"
mongosh · оболочкато же самое
db.products.updateOne(
  { _id: "p-101" },
  { $set: { price: 2690 } })
Вывод в терминалрезультат запуска
matched: 1 modified: 1

Одним $set можно задать сразу несколько полей, в том числе вложенных — через точку:

Python · PyMongoнесколько полей и вложенное
orders_box.update_one(
    {"_id": "o-0001"},
    {"$set": {
        "status": "доставлен",
        "payment.paid": True,        # поле внутри вложенного документа
        "delivery.days": 2,
    }})
C++ · mongocxxнесколько полей и вложенное
orders_box.update_one(
    make_document(kvp("_id", "o-0001")),
    make_document(kvp("$set", make_document(
        kvp("status", "доставлен"),
        kvp("payment.paid", true),   // поле внутри вложенного
        kvp("delivery.days", 2)))));
Go · mongo-driverнесколько полей и вложенное
ordersBox.UpdateOne(ctx,
    bson.D{{Key: "_id", Value: "o-0001"}},
    bson.D{{Key: "$set", Value: bson.D{
        {Key: "status", Value: "доставлен"},
        {Key: "payment.paid", Value: true},   // вложенное поле
        {Key: "delivery.days", Value: 2},
    }}})
Ruby · mongoнесколько полей и вложенное
orders_box.update_one(
  { "_id" => "o-0001" },
  { "$set" => {
      "status" => "доставлен",
      "payment.paid" => true,        # поле внутри вложенного
      "delivery.days" => 2
  } }
)
Осторожно: точка обязательна

Запись {"$set": {"payment": {"paid": True}}} не поменяет одно поле — она заменит весь вложенный документ payment на новый, в котором есть только paid. Способ оплаты при этом исчезнет. Чтобы изменить одно поле внутри, путь пишут через точку: "payment.paid".

§3matched против modified

Результат обновления возвращает два числа, и означают они разное.

Python · PyMongoто же значение второй раз
res = box.update_one({"_id": "p-101"}, {"$set": {"price": 2690}})
print("matched:", res.matched_count, "modified:", res.modified_count)
C++ · mongocxxто же значение второй раз
auto res = box.update_one(make_document(kvp("_id", "p-101")),
                     make_document(kvp("$set",
                         make_document(kvp("price", 2690)))));
std::cout << "matched: " << res->matched_count() << " modified: " << res->modified_count() << std::endl;
Go · mongo-driverто же значение второй раз
res, _ := box.UpdateOne(ctx, bson.D{{Key: "_id", Value: "p-101"}},
    bson.D{{Key: "$set", Value: bson.D{{Key: "price", Value: 2690}}}})
fmt.Println("matched:", res.MatchedCount, "modified:", res.ModifiedCount)
Ruby · mongoто же значение второй раз
res = box.update_one({ "_id" => "p-101" }, { "$set" => { "price" => 2690 } })
puts "matched: #{res.matched_count} modified: #{res.modified_count}"
Вывод в терминалвторой запуск
matched: 1 modified: 0
Вывод в терминалвторой запуск
matched: 1 modified: 0
Вывод в терминалвторой запуск
matched: 1 modified: 0
Вывод в терминалвторой запуск
matched: 1 modified: 0
matchedmodifiedЧто это значит
11Документ найден и изменён — обычный успешный случай
10Документ найден, но в поле уже лежало это же значение. Запись не понадобилась
00Фильтр не подошёл ни к одному документу. Менять было нечего

Проверять нужно число совпавших документов (matched), а не изменённых (modified) — как бы это поле ни называлось в вашем драйвере. API, который отвечает «объект не найден» по нулевому modified, ошибается каждый раз, когда пользователь сохранил форму, ничего в ней не изменив.

§4$inc и $unset

$set — не единственный оператор. Два других нужны почти так же часто.

$incПрибавить число к текущему значению. Отрицательное — вычесть. Если поля не было, оно появится с этим значением.счётчики
$unsetУдалить поле целиком. Значение справа не важно — пишут пустую строку.чистка
Python · PyMongo+1 к отзывам, скидка снята
res = box.update_one(
    {"_id": "p-003"},
    {"$inc": {"reviews": 1},
     "$unset": {"discount": ""}})

print("matched:", res.matched_count, "modified:", res.modified_count)
print(box.find_one({"_id": "p-003"},
                  {"title": 1, "reviews": 1, "discount": 1}))
C++ · mongocxx+1 к отзывам, скидка снята
auto res = box.update_one(
    make_document(kvp("_id", "p-003")),
    make_document(
        kvp("$inc", make_document(kvp("reviews", 1))),
        kvp("$unset", make_document(kvp("discount", "")))));

std::cout << "matched: " << res->matched_count() << std::endl;
Go · mongo-driver+1 к отзывам, скидка снята
res, _ := box.UpdateOne(ctx,
    bson.D{{Key: "_id", Value: "p-003"}},
    bson.D{
        {Key: "$inc", Value: bson.D{{Key: "reviews", Value: 1}}},
        {Key: "$unset", Value: bson.D{{Key: "discount", Value: ""}}},
    })

fmt.Println("matched:", res.MatchedCount, "modified:", res.ModifiedCount)

var doc bson.D
box.FindOne(ctx, bson.D{{Key: "_id", Value: "p-003"}},
    options.FindOne().SetProjection(bson.D{
        {Key: "title", Value: 1}, {Key: "reviews", Value: 1}, {Key: "discount", Value: 1}}),
).Decode(&doc)
fmt.Println(doc)
Ruby · mongo+1 к отзывам, скидка снята
res = box.update_one(
  { "_id" => "p-003" },
  { "$inc" => { "reviews" => 1 },
    "$unset" => { "discount" => "" } }
)

puts "matched: #{res.matched_count} modified: #{res.modified_count}"
puts box.find({ "_id" => "p-003" },
              projection: { "title" => 1, "reviews" => 1,
                            "discount" => 1 }).first.inspect
Вывод в терминалрезультат запуска
matched: 1 modified: 1
{'_id': 'p-003', 'title': 'Ноутбук HP Pavilion 14', 'reviews': 32}
Вывод в терминалрезультат запуска
matched: 1
Вывод в терминалрезультат запуска
matched: 1 modified: 1
{"_id":"p-003","title":"Ноутбук HP Pavilion 14","reviews":{"$numberInt":"32"}}
Вывод в терминалрезультат запуска
matched: 1 modified: 1
{"_id"=>"p-003", "title"=>"Ноутбук HP Pavilion 14", "reviews"=>32}

Было 31 отзыв, стало 32; поля discount в документе больше нет — не null, а именно нет. Разница существенная: фильтр { discount: { $exists: false } } найдёт такой товар, а { discount: null } — нет.

Способ «прочитать, прибавить, записать» теряет данные при одновременной работе: два процесса прочитают 31, оба запишут 32, и один отзыв пропадёт. $inc выполняется на сервере одним неделимым действием, поэтому такой потери не будет.

Ещё три оператора обновления: $min и $max записывают значение, только если оно меньше (больше) текущего, $rename переименовывает поле. Операторы для массивов — $push, $pull, $addToSet — разберём после главы о массивах в фильтрах.

§5update_many: пачкой

Одинаковая правка для всех подходящих документов — это update_many. Форма та же, меняется только метод.

Python · PyMongoбесплатная доставка аксессуаров
res = box.update_many(
    {"category": "аксессуары"},
    {"$set": {"free_shipping": True}})

print("matched:", res.matched_count, "modified:", res.modified_count)
C++ · mongocxxбесплатная доставка аксессуаров
auto res = box.update_many(
    make_document(kvp("category", "аксессуары")),
    make_document(kvp("$set",
        make_document(kvp("free_shipping", true)))));

std::cout << "matched: " << res->matched_count()
          << " modified: " << res->modified_count() << std::endl;
Go · mongo-driverбесплатная доставка аксессуаров
res, _ := box.UpdateMany(ctx,
    bson.D{{Key: "category", Value: "аксессуары"}},
    bson.D{{Key: "$set", Value: bson.D{{Key: "free_shipping", Value: true}}}})

fmt.Println("matched:", res.MatchedCount, "modified:", res.ModifiedCount)
Ruby · mongoбесплатная доставка аксессуаров
res = box.update_many(
  { "category" => "аксессуары" },
  { "$set" => { "free_shipping" => true } }
)

puts "matched: #{res.matched_count} modified: #{res.modified_count}"
Вывод в терминалрезультат запуска
matched: 6 modified: 6
Вывод в терминалрезультат запуска
matched: 6 modified: 6
Вывод в терминалрезультат запуска
matched: 6 modified: 6
Вывод в терминалрезультат запуска
matched: 6 modified: 6

Шесть документов: три исходных аксессуара, коврик и две позиции, добавленные в главе о вставке. Поле free_shipping появилось у них и только у них — остальные товары не изменились.

На полях: сначала посчитайте, потом меняйте

Перед любым update_many выполняют count_documents с тем же фильтром. Число показывает, скольких документов коснётся правка, до того как она произойдёт. Если вместо шести получилось шестьсот, фильтр написан не так, как задумано.

Python · PyMongoпроверка перед правкой
query = {"category": "аксессуары"}

print("будет изменено:", box.count_documents(query))   # 6
box.update_many(query, {"$set": {"free_shipping": True}})
C++ · mongocxxпроверка перед правкой
auto query = make_document(kvp("category", "аксессуары"));

std::cout << "будет изменено: "
          << box.count_documents(query.view()) << std::endl;   // 6
box.update_many(query.view(), make_document(kvp("$set",
    make_document(kvp("free_shipping", true)))));
Go · mongo-driverпроверка перед правкой
query := bson.D{{Key: "category", Value: "аксессуары"}}

count, _ := box.CountDocuments(ctx, query)
fmt.Println("будет изменено:", count)              // 6

box.UpdateMany(ctx, query,
    bson.D{{Key: "$set", Value: bson.D{{Key: "free_shipping", Value: true}}}})
Ruby · mongoпроверка перед правкой
query = { "category" => "аксессуары" }

puts "будет изменено: #{box.count_documents(query)}"   # 6
box.update_many(query, { "$set" => { "free_shipping" => true } })

§6upsert: обновить или создать

Параметр upsert=True означает: если документ не нашёлся, создать его. Один запрос вместо связки «проверить, потом вставить».

Python · PyMongoдокумента ещё нет
res = box.update_one(
    {"_id": "p-999"},
    {"$set": {"title": "Товар из upsert", "price": 100}},
    upsert=True)

print("matched:", res.matched_count,
      "modified:", res.modified_count,
      "upserted_id:", res.upserted_id)
C++ · mongocxxдокумента ещё нет
mongocxx::options::update options;
options.upsert(true);

auto res = box.update_one(
    make_document(kvp("_id", "p-999")),
    make_document(kvp("$set", make_document(
        kvp("title", "Товар из upsert"), kvp("price", 100)))),
    options);

std::cout << "matched: " << res->matched_count()
          << " upserted_id: " << res->upserted_id()->get_string().value << std::endl;
Go · mongo-driverдокумента ещё нет
res, _ := box.UpdateOne(ctx,
    bson.D{{Key: "_id", Value: "p-999"}},
    bson.D{{Key: "$set", Value: bson.D{
        {Key: "title", Value: "Товар из upsert"},
        {Key: "price", Value: 100}}}},
    options.UpdateOne().SetUpsert(true))

fmt.Println("matched:", res.MatchedCount,
    "modified:", res.ModifiedCount, "upserted_id:", res.UpsertedID)
Ruby · mongoдокумента ещё нет
res = box.update_one(
  { "_id" => "p-999" },
  { "$set" => { "title" => "Товар из upsert", "price" => 100 } },
  upsert: true
)

puts "matched: #{res.matched_count} modified: #{res.modified_count} " \
     "upserted_id: #{res.upserted_id}"
Вывод в терминалрезультат запуска
matched: 0 modified: 0 upserted_id: p-999
Вывод в терминалрезультат запуска
matched: 0 upserted_id: p-999
Вывод в терминалрезультат запуска
matched: 0 modified: 0 upserted_id: p-999
Вывод в терминалрезультат запуска
matched: 0 modified: 0 upserted_id: p-999

Ноль совпадений, ноль изменений — и новый документ. Ключ _id взят из фильтра: при вставке через upsert сервер собирает документ из полей фильтра с точным равенством и полей из $set.

Применение: счётчики и агрегаты, которые обновляются при каждом событии. Например, счётчик просмотров товара за день — не нужно проверять, есть ли уже запись за сегодня:

Python · PyMongoсчётчик, который создаётся сам
stats.update_one(
    {"product_id": "p-003", "date": today},
    {"$inc": {"views": 1}},
    upsert=True)

print(stats.find_one({"product_id": "p-003", "date": today}, {"_id": 0}))
C++ · mongocxxсчётчик, который создаётся сам
mongocxx::options::update options;
options.upsert(true);

stats.update_one(
    make_document(kvp("product_id", "p-003"), kvp("date", today)),
    make_document(kvp("$inc", make_document(kvp("views", 1)))),
    options);

mongocxx::options::find only;
only.projection(make_document(kvp("_id", 0)));
auto doc = stats.find_one(make_document(kvp("product_id", "p-003"), kvp("date", today)), only);
std::cout << bsoncxx::to_json(*doc) << std::endl;
Go · mongo-driverсчётчик, который создаётся сам
stats.UpdateOne(ctx,
    bson.D{{Key: "product_id", Value: "p-003"}, {Key: "date", Value: today}},
    bson.D{{Key: "$inc", Value: bson.D{{Key: "views", Value: 1}}}},
    options.UpdateOne().SetUpsert(true))

var doc bson.D
stats.FindOne(ctx,
    bson.D{{Key: "product_id", Value: "p-003"}, {Key: "date", Value: today}},
    options.FindOne().SetProjection(bson.D{{Key: "_id", Value: 0}}),
).Decode(&doc)
fmt.Println(doc)
Ruby · mongoсчётчик, который создаётся сам
stats.update_one(
  { "product_id" => "p-003", "date" => today },
  { "$inc" => { "views" => 1 } },
  upsert: true
)

puts stats.find({ "product_id" => "p-003", "date" => today },
                projection: { "_id" => 0 }).first.inspect
Вывод в терминалпервый запуск за день
{'product_id': 'p-003', 'date': '2026-09-14', 'views': 1}
Вывод в терминалпервый запуск за день
{ "product_id" : "p-003", "date" : "2026-09-14", "views" : 1 }
Вывод в терминалпервый запуск за день
{"date":"2026-09-14","product_id":"p-003","views":{"$numberInt":"1"}}
Вывод в терминалпервый запуск за день
{"date"=>"2026-09-14", "product_id"=>"p-003", "views"=>1}

§7Обновление без операторов

Если забыть $set и передать просто документ, драйвер не пропустит запрос дальше — во всех языках курса, кроме Ruby:

Ошибкаобновление без оператора
ValueError: update only works with $ operators
Ошибкаобновление без оператора
terminate called after throwing an instance of 'mongocxx::v_noabi::logic_error'
  what():  Invalid key 'price': update only works with $ operators and pipelines: generic server error
Ошибкаобновление без оператора
update document must contain key beginning with '$'
Предупреждениезапрос всё равно выполнен
WARN -- : Invalid update document provided. Updates documents must only contain only atomic modifiers. The "price" key is invalid.
matched: 1 modified: 1
{"_id"=>"p-001", "price"=>100}
Осторожно: в Ruby такой запрос проходит

Драйвер Ruby только пишет предупреждение в журнал и отправляет запрос. Сервер понимает документ без операторов как замену: у p-001 остались только _id и price, название, категория и склады исчезли. Ошибки нет, modified равен единице — поэтому предупреждение Invalid update document в выводе нужно читать, а не пропускать.

Метод replace_one при этом заменяет документ целиком. Все поля, которых нет в новом документе, исчезают; сохраняется только _id.

МетодЧто происходит с полями, которых нет в запросеКогда применяют
update_one с $setОстаются как былиПочти всегда: точечная правка
replace_oneУдаляютсяКогда клиент прислал документ целиком и он — источник истины

Ошибка «сохранили форму — половина полей пропала» — это почти всегда replace_one там, где нужен был $set.

§8Правка документа в Compass

В Compass документ правится прямо в списке: наведите на него курсор и нажмите карандаш. Поля меняются на месте, можно добавить новое или удалить существующее.

Документ p-003 в MongoDB Compass после обновления: reviews 32, поля discount нет
Compass · sandbox.products после $inc и $unset: отзывов стало 32, поля discount в документе больше нет — не null, а именно нет

Тот же документ можно поправить и мышью — прямо в списке:

карандашПоявляется справа вверху документа при наведении курсора. Включает режим правки прямо в списке.шаг 1
правка полейЗначение меняется на месте; рядом с полем появляются кнопки «добавить поле» и «удалить поле». Изменённые строки подсвечиваются.шаг 2
UPDATECompass показывает, что именно изменится, и сам собирает запрос с $set. Рядом — Cancel, чтобы отказаться.шаг 3

Правка мышью подходит для разбора и для единичного исправления; всё, что делается больше одного раза, должно быть запросом в коде.

Результат запроса из §4, где мы прибавили отзыв и сняли скидку:

Документ p-003 в Compass после обновления: reviews равно 32, поля discount в документе нет
Compass · sandbox.products после $inc и $unset: reviews: 32, а строки discount в документе больше нет

Поля discount на кадре нет вовсе: оно не стало пустым и не превратилось в null, а исчезло из документа. Так работает $unset; поэтому такой товар найдётся по фильтру {"discount": {"$exists": False}}.

§9Частые ошибки

Что написаноЧто происходитКак правильно
update_one({...}, {"price": 100})ValueError: update only works with $ operators{"$set": {"price": 100}}
{"$set": {"payment": {"paid": True}}}Вложенный документ заменён целиком, остальные его поля потеряны{"$set": {"payment.paid": True}}
Проверка результата по modified_count«Не найдено» при сохранении без измененийПроверять matched_count
update_many({}, {...})Изменены все документы коллекцииПустой фильтр — только осознанно; сначала count_documents
Прочитать, прибавить в коде, записатьПри одновременной работе изменения теряются$inc — сервер выполнит это неделимо
Что написаноЧто происходитКак правильно
Обновление без оператораInvalid key 'price': update only works with $ operators and pipelinesОборачивать в kvp("$set", …)
kvp("$set", make_document(kvp("payment", …)))Вложенный документ заменён целиком, остальные его поля потеряныПуть через точку: kvp("payment.paid", true)
Проверка результата по modified_count()«Не найдено» при сохранении без измененийПроверять matched_count()
update_many(make_document(), …)Изменены все документы коллекцииПустой фильтр — только осознанно; сначала count_documents
Прочитать, прибавить в коде, записатьПри одновременной работе изменения теряются$inc — сервер выполнит это неделимо
Что написаноЧто происходитКак правильно
UpdateOne(ctx, filter, bson.D{{"price", 100}})update document must contain key beginning with '$'bson.D{{"$set", bson.D{{"price", 100}}}}
$set со вложенным документом целикомВложенный документ заменён, остальные его поля потеряныПуть через точку: {Key: "payment.paid", Value: true}
Проверка результата по ModifiedCount«Не найдено» при сохранении без измененийПроверять MatchedCount
UpdateMany(ctx, bson.D{}, …)Изменены все документы коллекцииПустой фильтр — только осознанно; сначала CountDocuments
Прочитать, прибавить в коде, записатьПри одновременной работе изменения теряются$inc — сервер выполнит это неделимо
Что написаноЧто происходитКак правильно
update_one(filter, { "price" => 100 })WARN: Invalid update document provided. Updates documents must contain only atomic modifiers{ "$set" => { "price" => 100 } }
$set со вложенным документом целикомВложенный документ заменён, остальные его поля потеряныПуть через точку: "payment.paid" => true
Проверка результата по modified_count«Не найдено» при сохранении без измененийПроверять matched_count
update_many({}, …)Изменены все документы коллекцииПустой фильтр — только осознанно; сначала count_documents
Прочитать, прибавить в коде, записатьПри одновременной работе изменения теряются$inc — сервер выполнит это неделимо

§10Проверьте себя

Результат: matched: 1, modified: 0. Что произошло?

Документ нашёлся, но менять было нечего: в поле уже лежало то же значение. Запрос при этом выполнен успешно.

Чем {"$set": {"delivery.city": "Москва"}} отличается от {"$set": {"delivery": {"city": "Москва"}}}?

Первый меняет одно поле внутри delivery, остальные его поля сохраняются. Второй заменяет весь вложенный документ — type и days будут потеряны.

Почему счётчик просмотров стоит увеличивать через $inc, а не читать и записывать из кода?

Потому что между чтением и записью значение может изменить другой процесс, и одно из увеличений потеряется. $inc выполняется на сервере одним неделимым действием.

Что делает upsert=True, если документ не найден, и откуда берётся его _id?

Создаёт новый документ из полей фильтра с точным равенством и полей обновления. Ключ берётся из фильтра, если он там задан; иначе сервер сгенерирует ObjectId. Номер созданного документа возвращается в upserted_id.

Как удалить поле из документа и чем это отличается от записи None?

Удаляет $unset: {"$unset": {"discount": ""}}. После него поля в документе нет — его найдёт {"discount": {"$exists": False}}. Запись None оставляет поле со значением null, и это разные состояния.

§11Шпаргалка

Изменить поле col.update_one( {"_id": "p-101"}, {"$set": {"price": 2690}})
Счётчик и удаление поля {"$inc": {"reviews": 1}} {"$unset": {"discount": ""}}
Пачкой col.update_many( {"category": "аксессуары"}, {"$set": {...}})
Обновить или создать col.update_one(query, upd, upsert=True) res.upserted_id
Изменить поле col.update_one( make_document(kvp("_id", "p-101")), make_document(kvp("$set", make_document(kvp("price", 2690)))));
Счётчик и удаление поля kvp("$inc", make_document(kvp("reviews", 1))) kvp("$unset", make_document(kvp("discount", "")))
Пачкой col.update_many(filter, update); res->matched_count()
Обновить или создать mongocxx::options::update options; options.upsert(true); col.update_one(filter, update, options);
Изменить поле col.UpdateOne(ctx, bson.D{{Key: "_id", Value: "p-101"}}, bson.D{{Key: "$set", Value: bson.D{ {Key: "price", Value: 2690}}}})
Счётчик и удаление поля {Key: "$inc", Value: bson.D{{Key: "reviews", Value: 1}}} {Key: "$unset", Value: bson.D{{Key: "discount", Value: ""}}}
Пачкой res, _ := col.UpdateMany(ctx, filter, update) res.MatchedCount
Обновить или создать col.UpdateOne(ctx, filter, update, options.UpdateOne().SetUpsert(true)) res.UpsertedID
Изменить поле col.update_one( { "_id" => "p-101" }, { "$set" => { "price" => 2690 } })
Счётчик и удаление поля { "$inc" => { "reviews" => 1 } } { "$unset" => { "discount" => "" } }
Пачкой res = col.update_many(filter, update) res.matched_count
Обновить или создать col.update_one(query, upd, upsert: true) res.upserted_id

Итог главы

Обновление — это два документа: фильтр «кого» и операторы «что». $set задаёт значение, $inc прибавляет неделимо, $unset убирает поле. update_one меняет первый подходящий документ, update_many — все, поэтому фильтр стоит сначала проверить счётчиком. Проверять результат нужно по matched_count: ноль изменений не значит «не найдено». Осталась последняя из четырёх операций — удаление.

Глава 1.7 · Обновление документов · Справочник по MongoDB Автор: Макаров Максим Николаевич