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

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

Глава 1.3

1.3

Вставка
документов

Первая из четырёх операций над данными: положить документ в коллекцию. Разберём insert_one и insert_many, что возвращают эти методы, откуда берётся _id и что произойдёт, если в середине пачки попадётся плохой документ.

Категория
Операции над данными
Уровень
Начальный
Связанные темы
Документ и BSON, обновление, удаление
Зачем это знать
Класть данные так, чтобы их потом можно было найти и не задвоить

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

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

§1Песочница: где мы работаем

Начиная с этой главы мы меняем данные, поэтому работать будем в отдельной базе sandbox. В ней лежит копия коллекции товаров — те же 21 документ, что и в shop.products.

Python · PyMongoподключение к песочнице
from pymongo import MongoClient

client = MongoClient("mongodb://localhost:27017/")
box = client["sandbox"]["products"]     # здесь можно ломать

print(box.count_documents({}))          # 21
C++ · mongocxxподключение к песочнице
// mongocxx::instance уже создан в заготовке: второй экземпляр программа не переживёт
mongocxx::client client{mongocxx::uri{"mongodb://localhost:27017"}};
auto box = client["sandbox"]["products"];   // здесь можно ломать

std::cout << box.count_documents(make_document());   // 21
Go · mongo-driverподключение к песочнице
client, _ := mongo.Connect(options.Client().
    ApplyURI("mongodb://localhost:27017"))
box := client.Database("sandbox").Collection("products")  // можно ломать

count, _ := box.CountDocuments(ctx, bson.D{})
fmt.Println(count)                                     // 21
Ruby · mongoподключение к песочнице
require "mongo"

client = Mongo::Client.new("mongodb://localhost:27017/")
box = client.use("sandbox").database[:products]   # здесь можно ломать

puts box.count_documents({})                     # 21
Коллекция sandbox.products в MongoDB Compass: 21 документ, рядом в дереве базы shop, hh, logs, org
Compass · sandbox.products — та же 21 позиция, что и в shop.products, но эту копию можно менять

Правило работы: эталонные данные не трогаем. В проекте ту же роль играет отдельная база разработчика или контур тестирования — чтобы эксперимент не испортил то, на что смотрят остальные. Разъехавшаяся песочница восстанавливается одной командой из репозитория практик.

§2insert_one: один документ

Метод insert_one принимает обычный словарь Python и кладёт его в коллекцию как документ.

Python · PyMongoвставка одного документа
result = box.insert_one({
    "sku": "SKU-AC-022",
    "title": "Коврик для мыши XL",
    "brand": "OEM",
    "category": "аксессуары",
    "price": 1290,
})
C++ · mongocxxвставка одного документа
auto result = box.insert_one(make_document(
    kvp("sku", "SKU-AC-022"),
    kvp("title", "Коврик для мыши XL"),
    kvp("brand", "OEM"),
    kvp("category", "аксессуары"),
    kvp("price", 1290)));
Go · mongo-driverвставка одного документа
res, err := box.InsertOne(ctx, bson.D{
    {Key: "sku", Value: "SKU-AC-022"},
    {Key: "title", Value: "Коврик для мыши XL"},
    {Key: "brand", Value: "OEM"},
    {Key: "category", Value: "аксессуары"},
    {Key: "price", Value: 1290},
})
Ruby · mongoвставка одного документа
result = box.insert_one({
  "sku" => "SKU-AC-022",
  "title" => "Коврик для мыши XL",
  "brand" => "OEM",
  "category" => "аксессуары",
  "price" => 1290
})
mongosh · оболочкато же самое
db.products.insertOne({
  sku: "SKU-AC-022",
  title: "Коврик для мыши XL",
  brand: "OEM",
  category: "аксессуары",
  price: 1290
})

Поля _id мы не указали — и сервер добавил его сам. Ни коллекцию, ни базу заранее создавать не пришлось: если бы sandbox.products не существовала, она появилась бы в этот момент.

§3Что возвращает вставка

Возвращается не документ, а результат операции — объект с двумя полезными полями.

Python · PyMongoразбор результата
print("подтверждено:", result.acknowledged)
print("присвоенный _id:", result.inserted_id)
print("тип:", type(result.inserted_id).__name__)
C++ · mongocxxразбор результата
// результат — optional: пусто, если запись не подтверждена
std::cout << "подтверждено: " << (result ? "да" : "нет") << std::endl;
std::cout << "присвоенный _id: "
          << result->inserted_id().get_oid().value.to_string() << std::endl;
Go · mongo-driverразбор результата
// подтверждение в Go — это отсутствие ошибки
if err != nil {
    log.Fatal(err)
}
fmt.Println("присвоенный _id:", res.InsertedID)
Ruby · mongoразбор результата
puts "подтверждено: #{result.acknowledged?}"
puts "присвоенный _id: #{result.inserted_id}"
puts "тип: #{result.inserted_id.class}"
Вывод в терминалрезультат запуска
подтверждено: True
присвоенный _id: 6aa7242b708f072aeb471236
тип: ObjectId
Вывод в терминалрезультат запуска
подтверждено: да
присвоенный _id: 6aa72484d8d50a91a109a7f1
Вывод в терминалрезультат запуска
присвоенный _id: ObjectID("6aa72439598d2ddc60f4355e")
Вывод в терминалрезультат запуска
подтверждено: true
присвоенный _id: 6aa7760de597bcf16516ccbe
тип: BSON::ObjectId
acknowledgedСервер подтвердил запись. False бывает только при особых настройках, когда программа сознательно не ждёт подтверждения.подтверждение
inserted_idКлюч вставленного документа. Его сохраняют, чтобы потом сослаться на запись — например, вернуть в ответе API.ключ

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

§4insert_many: пачка документов

Документы по одному в цикле не кладут: каждый вызов идёт до сервера и обратно. Для пачки есть insert_many, отправляющий всё одним обращением.

Python · PyMongoвставка списка
many = box.insert_many([
    {"_id": "p-101", "title": "Подставка для ноутбука",
     "category": "аксессуары", "price": 2490},
    {"_id": "p-102", "title": "Чехол для планшета",
     "category": "аксессуары", "price": 1890},
])

print("вставлено:", len(many.inserted_ids), "→", many.inserted_ids)
print("документов в песочнице:", box.count_documents({}))
C++ · mongocxxвставка списка
std::vector<bsoncxx::document::value> batch;
batch.push_back(make_document(kvp("_id", "p-101"),
    kvp("title", "Подставка для ноутбука"),
    kvp("category", "аксессуары"), kvp("price", 2490)));
batch.push_back(make_document(kvp("_id", "p-102"),
    kvp("title", "Чехол для планшета"),
    kvp("category", "аксессуары"), kvp("price", 1890)));

auto many = box.insert_many(batch);
std::cout << "вставлено: " << many->inserted_count() << std::endl;
std::cout << "документов в песочнице: " << box.count_documents(make_document()) << std::endl;
Go · mongo-driverвставка списка
many, err := box.InsertMany(ctx, []any{
    bson.D{{Key: "_id", Value: "p-101"}, {Key: "title", Value: "Подставка для ноутбука"},
        {Key: "category", Value: "аксессуары"}, {Key: "price", Value: 2490}},
    bson.D{{Key: "_id", Value: "p-102"}, {Key: "title", Value: "Чехол для планшета"},
        {Key: "category", Value: "аксессуары"}, {Key: "price", Value: 1890}},
})
if err != nil {
    log.Fatal(err)
}

fmt.Println("вставлено:", len(many.InsertedIDs), "→", many.InsertedIDs)
count, _ := box.CountDocuments(ctx, bson.D{})
fmt.Println("документов в песочнице:", count)
Ruby · mongoвставка списка
many = box.insert_many([
  { "_id" => "p-101", "title" => "Подставка для ноутбука",
    "category" => "аксессуары", "price" => 2490 },
  { "_id" => "p-102", "title" => "Чехол для планшета",
    "category" => "аксессуары", "price" => 1890 }
])

puts "вставлено: #{many.inserted_count} → #{many.inserted_ids}"
puts "документов в песочнице: #{box.count_documents({})}"
Вывод в терминалрезультат запуска
вставлено: 2 → ['p-101', 'p-102']
документов в песочнице: 24
Вывод в терминалрезультат запуска
вставлено: 2
документов в песочнице: 24
Вывод в терминалрезультат запуска
вставлено: 2 → [p-101 p-102]
документов в песочнице: 24
Вывод в терминалрезультат запуска
вставлено: 2 → ["p-101", "p-102"]
документов в песочнице: 24

Двадцать четыре: к двадцати одному исходному товару добавился коврик из §2 и две новые позиции. Поле inserted_ids — список ключей в том же порядке, в каком шли документы.

На полях: почему не цикл

Тысяча вызовов insert_one — это тысяча обращений к серверу, и основное время занимает дорога до него. Один insert_many на тысячу документов отправляет их пачками и работает на порядок быстрее. Так устроена загрузка данных из файла, которой восстанавливается стенд.

§5Свой _id и его уникальность

В примере выше мы задали ключи сами — "p-101" и "p-102". Так делают, когда у объекта уже есть естественный идентификатор: артикул товара, номер заказа, логин пользователя. Тогда за уникальность отвечает программа, а сервер проверяет её при каждой вставке.

Python · PyMongoповторный ключ
from pymongo.errors import DuplicateKeyError

try:
    box.insert_one({"_id": "p-101", "title": "Ещё одна подставка"})
except DuplicateKeyError as error:
    print(type(error).__name__)
    print(error.details["errmsg"])
C++ · mongocxxповторный ключ
#include <mongocxx/exception/bulk_write_exception.hpp>

try {
    box.insert_one(make_document(
        kvp("_id", "p-101"), kvp("title", "Ещё одна подставка")));
} catch (const mongocxx::bulk_write_exception& error) {
    std::cout << "код ошибки: " << error.code().value() << std::endl;
    std::cout << error.what() << std::endl;
}
Go · mongo-driverповторный ключ
_, err = box.InsertOne(ctx, bson.D{
    {Key: "_id", Value: "p-101"},
    {Key: "title", Value: "Ещё одна подставка"},
})

if mongo.IsDuplicateKeyError(err) {
    fmt.Println("дубликат ключа:", err)
}
Ruby · mongoповторный ключ
begin
  box.insert_one({ "_id" => "p-101", "title" => "Ещё одна подставка" })
rescue Mongo::Error::OperationFailure => error
  puts error.class
  puts error.message
end
Вывод в терминалрезультат запуска
DuplicateKeyError
E11000 duplicate key error collection: sandbox.products index: _id_ dup key: { _id: "p-101" }
Вывод в терминалрезультат запуска
код ошибки: 11000
E11000 duplicate key error collection: sandbox.products index: _id_ dup key: { _id: "p-101" }: generic server error
Вывод в терминалрезультат запуска
дубликат ключа: write exception: write errors: [E11000 duplicate key error collection: sandbox.products index: _id_ dup key: { _id: "p-101" }]
Вывод в терминалрезультат запуска
Mongo::Error::OperationFailure
[11000]: E11000 duplicate key error collection: sandbox.products index: _id_ dup key: { _id: "p-101" } (on localhost:27017, legacy retry, attempt 1)

Код E11000 означает, что сервер не дал записать второй документ с тем же ключом. Читается сообщение целиком: коллекция sandbox.products, индекс _id_, значение "p-101". Тот же код вы увидите и при нарушении любого другого уникального индекса — например, если запретите повторяющиеся артикулы.

§6Порядок вставки: ordered

Поведение пачки, в которой десятый документ из ста оказался с повторяющимся ключом, задаётся параметром ordered. По умолчанию он равен True.

РежимПоведениеКогда выбирают
ordered=True
по умолчанию
Документы идут по порядку. На первой ошибке вставка прекращается: первые девять записаны, десятый и все следующие — нет Когда порядок важен или когда лучше остановиться и разобраться
ordered=False Сервер пытается вставить все документы, плохие пропускает, а в конце сообщает обо всех ошибках сразу Загрузка данных из внешнего источника: пусть войдёт всё, что можно, а сбойные строки разберём потом
Python · PyMongoзагрузка с пропуском сбойных
from pymongo.errors import BulkWriteError

batch = [
    {"_id": "p-103", "title": "Кабель HDMI 2 м", "category": "аксессуары", "price": 690},
    {"_id": "p-101", "title": "Подставка — повтор"},   # такой _id уже есть
    {"_id": "p-104", "title": "Салфетки для экрана", "category": "аксессуары", "price": 390},
]

try:
    box.insert_many(batch, ordered=False)
except BulkWriteError as error:
    failed = error.details["writeErrors"]
    print(f"не вставлено: {len(failed)} из {len(batch)}")

print("документов в песочнице:", box.count_documents({}))
C++ · mongocxxзагрузка с пропуском сбойных
std::vector<bsoncxx::document::value> batch;
batch.push_back(make_document(kvp("_id", "p-103"), kvp("title", "Кабель HDMI 2 м"),
                               kvp("category", "аксессуары"), kvp("price", 690)));
batch.push_back(make_document(kvp("_id", "p-101"), kvp("title", "Подставка — повтор")));  // такой _id уже есть
batch.push_back(make_document(kvp("_id", "p-104"), kvp("title", "Салфетки для экрана"),
                               kvp("category", "аксессуары"), kvp("price", 390)));

mongocxx::options::insert options;
options.ordered(false);

try {
    box.insert_many(batch, options);
} catch (const mongocxx::bulk_write_exception& error) {
    std::cout << "часть документов не прошла: "
              << error.what() << std::endl;
}

std::cout << "документов в песочнице: " << box.count_documents(make_document()) << std::endl;
Go · mongo-driverзагрузка с пропуском сбойных
batch := []any{
    bson.D{{Key: "_id", Value: "p-103"}, {Key: "title", Value: "Кабель HDMI 2 м"}, {Key: "category", Value: "аксессуары"}, {Key: "price", Value: 690}},
    bson.D{{Key: "_id", Value: "p-101"}, {Key: "title", Value: "Подставка — повтор"}}, // такой _id уже есть
    bson.D{{Key: "_id", Value: "p-104"}, {Key: "title", Value: "Салфетки для экрана"}, {Key: "category", Value: "аксессуары"}, {Key: "price", Value: 390}},
}

options := options.InsertMany().SetOrdered(false)

_, err := box.InsertMany(ctx, batch, options)
var bulkErr mongo.BulkWriteException
if errors.As(err, &bulkErr) {
    fmt.Printf("не вставлено: %d из %d\n", len(bulkErr.WriteErrors), len(batch))
}

count, _ := box.CountDocuments(ctx, bson.D{})
fmt.Println("документов в песочнице:", count)
Ruby · mongoзагрузка с пропуском сбойных
batch = [
  { "_id" => "p-103", "title" => "Кабель HDMI 2 м", "category" => "аксессуары", "price" => 690 },
  { "_id" => "p-101", "title" => "Подставка — повтор" },   # такой _id уже есть
  { "_id" => "p-104", "title" => "Салфетки для экрана", "category" => "аксессуары", "price" => 390 },
]

begin
  box.insert_many(batch, ordered: false)
rescue Mongo::Error::BulkWriteError => error
  failed = error.result["writeErrors"]
  puts "не вставлено: #{failed.size} из #{batch.size}"
end

puts "документов в песочнице: #{box.count_documents({})}"
Вывод в терминалпачка из трёх документов, второй — повтор p-101
не вставлено: 1 из 3
документов в песочнице: 26
Вывод в терминалпачка из трёх документов, второй — повтор p-101
часть документов не прошла: E11000 duplicate key error collection: sandbox.products index: _id_ dup key: { _id: "p-101" }: generic server error
документов в песочнице: 26
Вывод в терминалпачка из трёх документов, второй — повтор p-101
не вставлено: 1 из 3
документов в песочнице: 26
Вывод в терминалпачка из трёх документов, второй — повтор p-101
не вставлено: 1 из 3
документов в песочнице: 26

В пачке три документа, и второй повторяет _id товара из §4. Два записаны, один отклонён — в песочнице стало на два документа больше. Исключение означает, что записались не все документы, — часть их уже лежит в коллекции. Это учитывают при повторном запуске загрузчика.

§7Вставка руками в Compass

Тот же документ можно положить мышью: в коллекции есть кнопка ADD DATA → Insert document. Compass откроет окно, в котором документ пишется как JSON.

ADD DATAЗелёная кнопка над списком документов. В меню два пункта: вставить документ и загрузить файл.шаг 1
Insert documentОткрывает окно с редактором JSON. Поле _id уже заполнено новым ObjectId — его можно оставить или заменить своим.шаг 2
InsertКнопка остаётся серой, пока JSON некорректен: пропущенная запятая или незакрытая кавычка не дадут отправить документ.шаг 3

Проверка JSON до отправки — тот же контроль, что и в строке фильтра: база не получит заведомо неразбираемый документ. После вставки счётчик коллекции увеличивается на единицу, а новый документ появляется в списке — с _id типа ObjectId, если вы не задали ключ сами.

§8Загрузка файла целиком

Данные редко вводят руками. Обычно это файл — выгрузка из другой системы, экспорт коллекции, набор для стенда. Способов два.

Терминалmongoimport: массив JSON
mongoimport --uri "mongodb://localhost:27017" \
  --db sandbox --collection products \
  --file stend/seed/sandbox.products.json --jsonArray --drop
Python · PyMongoтот же файл из кода
from bson import json_util      # понимает {"$date": …} в файле

with open("../stend/seed/sandbox.products.json", encoding="utf-8") as handle:
    docs = json_util.loads(handle.read())

box.delete_many({})           # повтор без задвоения
box.insert_many(docs)
print("документов в песочнице:", box.count_documents({}))
C++ · mongocxxтот же файл из кода
std::ifstream file{"../stend/seed/sandbox.products.json"};
std::stringstream buffer;
buffer << file.rdbuf();

auto array = bsoncxx::from_json(buffer.str());
std::vector<bsoncxx::document::value> docs;
for (const auto& item : array.view())
    docs.push_back(bsoncxx::document::value{item.get_document().value});

box.delete_many(make_document());   // повтор без задвоения
box.insert_many(docs);
std::cout << "документов в песочнице: " << box.count_documents(make_document()) << std::endl;
Go · mongo-driverтот же файл из кода
raw, _ := os.ReadFile("../stend/seed/sandbox.products.json")

var docs []any
bson.UnmarshalExtJSON(raw, false, &docs)

box.DeleteMany(ctx, bson.D{})      // повтор без задвоения
box.InsertMany(ctx, docs)
count, _ := box.CountDocuments(ctx, bson.D{})
fmt.Println("документов в песочнице:", count)
Ruby · mongoтот же файл из кода
# BSON::ExtJSON понимает {"$date": …} в файле, обычный JSON.parse — нет
docs = BSON::ExtJSON.parse(File.read("../stend/seed/sandbox.products.json"))

box.delete_many({})             # повтор без задвоения
box.insert_many(docs)
puts "документов в песочнице: #{box.count_documents({})}"
Вывод в терминалпесочница снова из файла стенда
документов в песочнице: 21
Вывод в терминалпесочница снова из файла стенда
документов в песочнице: 21
Вывод в терминалпесочница снова из файла стенда
документов в песочнице: 21
Вывод в терминалпесочница снова из файла стенда
документов в песочнице: 21

Путь в коде начинается с ../, потому что пример запускается из папки lessons, а файл лежит в stend/seed того же репозитория. Файл записан в Extended JSON, поэтому читается не обычным разбором JSON, а функцией драйвера: иначе даты из {"$date": …} попадут в базу вложенными документами.

Ключ --drop у mongoimport и delete_many({}) в коде делают одно и то же: очищают коллекцию перед загрузкой. Без этого повторный запуск удвоит данные. Обычно числа в работах расходятся именно по этой причине: загрузку выполнили дважды.

В Compass тому же соответствует ADD DATA → Import JSON or CSV file. Файл может быть массивом документов или потоком объектов по одному на строку — Compass поймёт оба формата.

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

Что написаноЧто происходитКак правильно
insert_one([...]) со спискомTypeError: document must be an instance of dictСписок — это insert_many
doc = insert_one({...})В переменной результат операции, а не документЧитать обратно: find_one({"_id": result.inserted_id})
Повторный запуск загрузчикаДокументов вдвое больше, числа в отчётах не сходятсяОчищать коллекцию или задавать свой _id, чтобы повтор упёрся в E11000
insert_one в цикле на тысячу документовРаботает, но медленно: каждый вызов — обращение к серверуСобрать список и вставить одним insert_many
Тот же словарь вставлен дваждыDuplicateKeyError: драйвер дописал _id в исходный словарьКопировать словарь или создавать новый для каждой записи
Что написаноЧто происходитКак правильно
insert_one с уже использованным document::valueКомпилируется, но документ уходит в перемещённом состоянииСобирать документ заново или передавать view()
Результат не проверен на пустотуРазыменование пустого optional — неопределённое поведениеif (result) result->inserted_id()
Повторный запуск загрузчикаДокументов вдвое больше, числа в отчётах не сходятсяОчищать коллекцию или задавать свой _id, чтобы повтор упёрся в E11000
insert_one в цикле на тысячу документовРаботает, но медленно: каждый вызов — обращение к серверуСобрать std::vector<document::value> и вставить insert_many
Исключение не пойманоПрограмма падает на первом дубликате ключаcatch (const mongocxx::bulk_write_exception&) и разбор code().value()
Что написаноЧто происходитКак правильно
InsertMany с одним документом вместо срезаinvalid documents: must provide a non-empty sliceПервым аргументом идёт срез, даже если документ один
Ошибка вставки не проверенаДокумент не записан, программа идёт дальшеif err != nil сразу после вызова
Повторный запуск загрузчикаДокументов вдвое больше, числа в отчётах не сходятсяОчищать коллекцию или задавать свой _id, чтобы повтор упёрся в E11000
InsertOne в цикле на тысячу документовРаботает, но медленно: каждый вызов — обращение к серверуСобрать срез и вставить одним InsertMany
err != nil считается полным проваломПри ordered=false часть документов уже записанаРазобрать mongo.BulkWriteException и посмотреть WriteErrors
Что написаноЧто происходитКак правильно
insert_one([...]) со спискомСписок записывается как один документ или вызывает ошибку драйвераСписок — это insert_many
doc = insert_one({...})В переменной результат операции, а не документЧитать обратно: col.find({ "_id" => result.inserted_id }).first
Повторный запуск загрузчикаДокументов вдвое больше, числа в отчётах не сходятсяОчищать коллекцию или задавать свой _id, чтобы повтор упёрся в E11000
rescue Mongo::Error::OperationFailure на пачкеИсключение проходит мимо: частичный отказ — это BulkWriteErrorrescue Mongo::Error::BulkWriteError, список неудач в error.result["writeErrors"]
insert_one в цикле на тысячу документовРаботает, но медленно: каждый вызов — обращение к серверуСобрать массив и вставить одним insert_many
Осторожно: словарь меняется на месте

Последняя строка таблицы требует пояснения. PyMongo дописывает сгенерированный _id прямо в переданный словарь. Поэтому for _ in range(3): box.insert_one(template) вставит один документ, а на втором обороте упадёт с DuplicateKeyError: в template уже лежит _id от первой вставки.

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

Мы вставили документ в коллекцию, которой не существовало. Что произошло?

Коллекция создалась сама в момент вставки — а если не существовало и базы, то и база. Отдельной команды на создание не требуется.

Что вернёт insert_one и как получить сам вставленный документ?

Вернёт объект результата с полями acknowledged и inserted_id. Документ читается обратно: box.find_one({"_id": result.inserted_id}).

В пачке из 100 документов десятый нарушает уникальность. Сколько документов окажется в базе при ordered=True и при ordered=False?

При ordered=True — девять: вставка останавливается на первой ошибке. При ordered=False — девяносто девять: сервер пропустит сбойный документ и вставит остальные, а в исключении перечислит все неудачи.

Загрузчик запустили дважды, и товаров стало 42 вместо 21. Что делать и как не повторить?

Очистить коллекцию и загрузить заново: delete_many({}) или mongoimport --drop. Чтобы повтор был безопасным, задавайте документам собственный _id — тогда вторая загрузка остановится на E11000 и копий не создаст.

Зачем на стенде курса отдельная база sandbox?

Чтобы упражнения не меняли данные, на которых построены примеры книги и ответы к практикам. В проекте у этого приёма то же название и та же цель: отдельный контур для экспериментов.

§11Шпаргалка

Один документ res = col.insert_one({...}) res.inserted_id # mongosh: insertOne({...})
Пачка res = col.insert_many([...]) res.inserted_ids # insertMany([...])
Не падать на сбойных col.insert_many( docs, ordered=False) # ловить BulkWriteError
Перезалить коллекцию col.delete_many({}) col.insert_many(docs) # или mongoimport --drop
Один документ auto res = col.insert_one( make_document(kvp("price", 1290))); res->inserted_id()
Пачка std::vector<bsoncxx::document::value> batch; auto many = col.insert_many(batch); many->inserted_count()
Не падать на сбойных mongocxx::options::insert options; options.ordered(false); // ловить bulk_write_exception
Перезалить коллекцию col.delete_many(make_document()); col.insert_many(docs);
Один документ res, err := col.InsertOne(ctx, bson.D{{Key: "price", Value: 1290}}) res.InsertedID
Пачка many, err := col.InsertMany(ctx, docs) many.InsertedIDs
Не падать на сбойных col.InsertMany(ctx, docs, options.InsertMany().SetOrdered(false)) // errors.As(&mongo.BulkWriteException{})
Перезалить коллекцию col.DeleteMany(ctx, bson.D{}) col.InsertMany(ctx, docs)
Один документ res = col.insert_one({ ... }) res.inserted_id
Пачка res = col.insert_many([...]) res.inserted_ids res.inserted_count
Не падать на сбойных col.insert_many(docs, ordered: false) # rescue Mongo::Error::OperationFailure
Перезалить коллекцию col.delete_many({}) col.insert_many(docs)

Итог главы

insert_one кладёт один документ и возвращает его ключ, insert_many — список документов одним обращением к серверу. База и коллекция создаются сами при первой записи, а _id либо задаёте вы, либо генерирует сервер. Уникальность ключа проверяется всегда: повтор даёт ошибку E11000 — это защита от задвоения. Данные в базе есть — в следующей главе научимся их доставать.

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