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

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

Глава 1.4

1.4

Чтение:
find и курсор

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

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

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

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

§1find_one: первый подходящий

Метод find_one возвращает один документ — как обычный словарь Python — или None, если ничего не нашлось.

Python · PyMongoодин документ
products = client["shop"]["products"]

one = products.find_one({"category": "смартфоны"})
print(one["title"], one["price"])
C++ · mongocxxодин документ
auto products = client["shop"]["products"];

auto one = products.find_one(make_document(kvp("category", "смартфоны")));
std::cout << one->view()["title"].get_string().value << std::endl;
Go · mongo-driverтип товара для примеров главы
// Product — поля товара, которые читают примеры. Тип объявлен в главе 1.4.
type Product struct {
    ID       string `bson:"_id"`
    SKU      string `bson:"sku"`
    Title    string `bson:"title"`
    Category string `bson:"category"`
    Price    int    `bson:"price"`
}
Go · mongo-driverодин документ
products := client.Database("shop").Collection("products")

var one Product
err := products.FindOne(ctx,
    bson.D{{Key: "category", Value: "смартфоны"}}).Decode(&one)
if err != nil {
    log.Fatal(err)
}
fmt.Println(one.Title, one.Price)
Ruby · mongoодин документ
products = client.use("shop").database[:products]

one = products.find({ "category" => "смартфоны" }).first
puts "#{one['title']} #{one['price']}"
Вывод в терминалрезультат запуска
Смартфон Xiaomi Redmi Note 13 18990
Вывод в терминалрезультат запуска
Смартфон Xiaomi Redmi Note 13
Вывод в терминалрезультат запуска
Смартфон Xiaomi Redmi Note 13 18990
Вывод в терминалрезультат запуска
Смартфон Xiaomi Redmi Note 13 18990

Смартфонов в коллекции четыре, а вернулся один — тот, который сервер нашёл первым. Какой именно — не определено: без сортировки «первый» означает «первый попавшийся» — ни цена, ни дата на выбор не влияют. Если вам нужен конкретный, добавляйте сортировку.

Когда совпадений нет, исключения не будет:

Python · PyMongoничего не нашлось
missing = products.find_one({"category": "велосипеды"})
print(missing)          # None

# поэтому обращаться к полям можно только после проверки
if missing is not None:
    print(missing["title"])
C++ · mongocxxничего не нашлось
auto missing = products.find_one(make_document(kvp("category", "велосипеды")));

// find_one возвращает optional: пустой, если ничего не нашлось
if (missing) {
    std::cout << missing->view()["title"].get_string().value;
}
Go · mongo-driverничего не нашлось
var missing Product
err := products.FindOne(ctx,
    bson.D{{Key: "category", Value: "велосипеды"}}).Decode(&missing)

// «не нашлось» — это отдельная ошибка, а не пустая структура
if errors.Is(err, mongo.ErrNoDocuments) {
    fmt.Println("ничего не нашлось")
}
Ruby · mongoничего не нашлось
missing = products.find({ "category" => "велосипеды" }).first
puts missing.inspect     # nil

# поэтому обращаться к полям можно только после проверки
puts missing["title"] unless missing.nil?

§2Фильтр — это документ

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

{}Пустой фильтр — условий нет, подходят все документы коллекции.21 из 21
{"category": "ноутбуки"}Поле category в точности равно этой строке.5 из 21
{"category": "ноутбуки", "brand": "HP"}Два поля в одном документе — это «и». Оба условия должны выполниться.1 из 21

Перечисление полей через запятую — это всегда «и», отдельного оператора для такого случая не нужно. «Или», сравнения и условия по массивам — тоже документы, только со специальными полями-операторами; им посвящён весь модуль 2.

На полях: почему это удобно

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

Python · PyMongoфильтр собирается по частям
def search(products, category=None, brand=None):
    query = {}
    if category:
        query["category"] = category
    if brand:
        query["brand"] = brand
    return list(products.find(query))

found = search(products, category="смартфоны")
print("смартфонов:", len(found))
C++ · mongocxxфильтр собирается по частям
auto search(mongocxx::collection& products, std::string category, std::string brand) {
    bsoncxx::builder::basic::document query{};
    if (!category.empty()) query.append(kvp("category", category));
    if (!brand.empty()) query.append(kvp("brand", brand));
    return products.find(query.extract());
}

// в main:
int found = 0;
for (auto&& doc : search(products, "смартфоны", "")) ++found;
std::cout << "смартфонов: " << found << std::endl;
Go · mongo-driverфильтр собирается по частям
func search(ctx context.Context, products *mongo.Collection, category, brand string) ([]Product, error) {
    query := bson.D{}
    if category != "" {
        query = append(query, bson.E{Key: "category", Value: category})
    }
    if brand != "" {
        query = append(query, bson.E{Key: "brand", Value: brand})
    }

    cursor, err := products.Find(ctx, query)
    if err != nil {
        return nil, err
    }
    var list []Product
    return list, cursor.All(ctx, &list)
}

// в main:
found, _ := search(ctx, products, "смартфоны", "")
fmt.Println("смартфонов:", len(found))
Ruby · mongoфильтр собирается по частям
def search(products, category: nil, brand: nil)
  query = {}
  query["category"] = category if category
  query["brand"] = brand if brand
  products.find(query).to_a
end

found = search(products, category: "смартфоны")
puts "смартфонов: #{found.size}"
Вывод в терминалвызов search
смартфонов: 4
Вывод в терминалвызов search
смартфонов: 4
Вывод в терминалвызов search
смартфонов: 4
Вывод в терминалвызов search
смартфонов: 4

§3find: курсор, а не список

Метод find возвращает все подходящие документы — но не сразу и не списком. Он возвращает курсор: объект, который будет подтягивать документы с сервера по мере того, как вы их перебираете.

Python · PyMongoперебор курсора
cursor = products.find({"category": "смартфоны"})
print(type(cursor).__name__)

for doc in cursor:
    print(doc["_id"], doc["title"], doc["price"])
C++ · mongocxxперебор курсора
auto cursor = products.find(make_document(kvp("category", "смартфоны")));

for (const auto& doc : cursor) {
    std::cout << doc["_id"].get_string().value << " "
              << doc["price"].get_int32().value << std::endl;
}
Go · mongo-driverперебор курсора
cursor, _ := products.Find(ctx, bson.D{{Key: "category", Value: "смартфоны"}})
fmt.Printf("тип: %T\n", cursor)

for cursor.Next(ctx) {
    var p Product
    cursor.Decode(&p)
    fmt.Println(p.ID, p.Title, p.Price)
}
Ruby · mongoперебор курсора
view = products.find({ "category" => "смартфоны" })
puts "класс: #{view.class}"

view.each { |doc| puts "#{doc['_id']} #{doc['title']} #{doc['price']}" }
Вывод в терминалрезультат запуска
Cursor
p-006 Смартфон Xiaomi Redmi Note 13 18990
p-007 Смартфон Samsung Galaxy A55 32990
p-008 Смартфон Apple iPhone 15 79990
p-009 Смартфон realme 12 Pro 27490
Вывод в терминалрезультат запуска
p-006 18990
p-007 32990
p-008 79990
p-009 27490
Вывод в терминалрезультат запуска
тип: *mongo.Cursor
p-006 Смартфон Xiaomi Redmi Note 13 18990
p-007 Смартфон Samsung Galaxy A55 32990
p-008 Смартфон Apple iPhone 15 79990
p-009 Смартфон realme 12 Pro 27490
Вывод в терминалрезультат запуска
класс: Mongo::Collection::View
p-006 Смартфон Xiaomi Redmi Note 13 18990
p-007 Смартфон Samsung Galaxy A55 32990
p-008 Смартфон Apple iPhone 15 79990
p-009 Смартфон realme 12 Pro 27490

Список вместо курсора означал бы, что все подходящие документы — а их может быть миллион — сразу окажутся в памяти. Курсор забирает данные пачками (по умолчанию первая пачка — 101 документ), а следующую запрашивает, когда предыдущая закончилась. Программа при этом работает с обычным перебором и о пачках не знает.

Если документов заведомо немного и они нужны все сразу, курсор превращают в список явно:

Python · PyMongoкурсор → список
items = list(products.find({"category": "смартфоны"}))
print(len(items))        # 4
C++ · mongocxxкурсор → список
std::vector<bsoncxx::document::value> items;
for (const auto& doc : products.find(
        make_document(kvp("category", "смартфоны"))))
    items.emplace_back(doc);

std::cout << items.size() << std::endl;   // 4
Go · mongo-driverкурсор → список
cursor, _ := products.Find(ctx,
    bson.D{{Key: "category", Value: "смартфоны"}})

var items []Product
cursor.All(ctx, &items)
fmt.Println(len(items))        // 4
Ruby · mongoкурсор → список
items = products.find({ "category" => "смартфоны" }).to_a
puts items.size            # 4

§4Курсор проходится один раз

Курсор одноразовый: он помнит, где остановился, и после полного прохода становится пустым. Если принять курсор за список, второй проход вернёт пустоту.

Курсор одноразовый: mongocxx::cursor можно обойти ровно один раз, повторный for по тому же объекту не даст ни одного документа.

Курсор одноразовый: после того как Next вернул false, курсор исчерпан. Метод All тоже дочитывает его до конца — второй раз данных не будет.

В Ruby find возвращает не курсор, а представление Mongo::Collection::View — описание запроса. Каждый обход выполняет запрос заново, поэтому повторная итерация данные вернёт. Курсор внутри одноразовый и здесь: просто драйвер создаёт новый.

Python · PyMongoвторой проход
cursor = products.find({"category": "смартфоны"})

for doc in cursor:
    print(doc["_id"])       # четыре строки

print("второй проход:", list(cursor))
C++ · mongocxxвторой проход
auto cursor = products.find(make_document(kvp("category", "смартфоны")));

for (const auto& doc : cursor) { /* четыре документа */ }

int second = 0;
for (const auto& doc : cursor) ++second;
std::cout << "второй проход: " << second << std::endl;
Go · mongo-driverвторой проход
cursor, _ := products.Find(ctx, bson.D{{Key: "category", Value: "смартфоны"}})

for cursor.Next(ctx) {
    // четыре документа
}

fmt.Println("второй проход:", cursor.Next(ctx))
Ruby · mongoвторой проход
view = products.find({ "category" => "смартфоны" })

view.each { |doc| puts doc["_id"] }   # четыре строки

# запрос выполняется заново — документы те же
puts "второй проход: #{view.to_a.size} документов"
Вывод в терминалрезультат запуска
p-006
p-007
p-008
p-009
второй проход: []
Вывод в терминалрезультат запуска
второй проход: 0
Вывод в терминалрезультат запуска
второй проход: false
Вывод в терминалрезультат запуска
p-006
p-007
p-008
p-009
второй проход: 4 документов

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

Так не работает · курсор уже пройденPython
cursor = products.find({"category": "ноутбуки"})

print("найдено:", len(list(cursor)))   # 5
for doc in cursor:                  # ни одной строки
    print(doc["title"])
Так не работает · курсор уже пройденC++
auto cursor = products.find(make_document(kvp("category", "ноутбуки")));

auto found = std::distance(cursor.begin(), cursor.end());   // 5
for (const auto& doc : cursor) {                    // ни одного
    std::cout << doc["title"].get_string().value;
}
Так не работает · курсор уже пройденGo
cursor, _ := products.Find(ctx, bson.D{{Key: "category", Value: "ноутбуки"}})

var list []Product
cursor.All(ctx, &list)              // 5, курсор дочитан

for cursor.Next(ctx) {              // ни одной итерации
    // сюда уже не попадём
}
Так не работает · курсор уже пройденRuby
# в Ruby эта ловушка не срабатывает: view перезапрашивает данные
view = products.find({ "category" => "ноутбуки" })

puts "найдено: #{view.to_a.size}"   # 5, первый запрос
view.each { |doc| puts doc["title"] }  # 5, второй запрос
Так работает · один проход в памятьPython
items = list(products.find({"category": "ноутбуки"}))

print("найдено:", len(items))          # 5
for doc in items:                      # пять строк
    print(doc["title"])
Так работает · один проход в памятьC++
std::vector<bsoncxx::document::value> items;
for (const auto& doc : products.find(make_document(kvp("category", "ноутбуки"))))
    items.emplace_back(doc);

std::cout << "найдено: " << items.size() << std::endl;   // 5
for (const auto& doc : items) { /* пять раз */ }
Так работает · один проход в памятьGo
cursor, _ := products.Find(ctx, bson.D{{Key: "category", Value: "ноутбуки"}})

var items []Product
cursor.All(ctx, &items)

fmt.Println("найдено:", len(items))    // 5
for _, doc := range items {          // пять строк
    fmt.Println(doc.Title)
}
Так работает · один проход в памятьRuby
items = products.find({ "category" => "ноутбуки" }).to_a

puts "найдено: #{items.size}"         # 5, один запрос к серверу
items.each { |doc| puts doc["title"] }  # пять строк, без запроса

Считать количество через len(list(...)) всё же не нужно: ради одного числа на клиент приедут все документы. Для этого есть count_documents(filter) — сервер посчитает сам и вернёт одно число. О нём подробно в главе 1.6.

§5Порядок без сортировки не определён

В выводе из §3 документы идут не по ключу и не по цене — а у разных драйверов ещё и в разном порядке. Без явной сортировки сервер не обещает никакого порядка: он отдаёт документы так, как ему удобнее их читать.

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

Python · PyMongoявный порядок
for doc in products.find({"category": "смартфоны"}).sort("price", 1):
    print(doc["price"], doc["title"])
C++ · mongocxxявный порядок
mongocxx::options::find options;
options.sort(make_document(kvp("price", 1)));

for (const auto& doc : products.find(
        make_document(kvp("category", "смартфоны")), options)) {
    std::cout << doc["price"].get_int32().value << std::endl;
}
Go · mongo-driverявный порядок
cursor, _ := products.Find(ctx,
    bson.D{{Key: "category", Value: "смартфоны"}},
    options.Find().SetSort(bson.D{{Key: "price", Value: 1}}))

var list []Product
cursor.All(ctx, &list)
for _, doc := range list {
    fmt.Println(doc.Price, doc.Title)
}
Ruby · mongoявный порядок
products.find({ "category" => "смартфоны" })
        .sort({ "price" => 1 })
        .each { |doc| puts "#{doc['price']} #{doc['title']}" }

§6Точечная нотация: внутрь документа

Условие можно поставить и на вложенное поле — путь пишется через точку и берётся в кавычки целиком.

Python · PyMongoвложенное поле
orders = client["shop"]["orders"]

# доставка в Екатеринбург — 28 заказов
orders.count_documents({"delivery.city": "Екатеринбург"})

# оплаченные заказы
orders.count_documents({"payment.paid": True})
C++ · mongocxxвложенное поле
auto orders = client["shop"]["orders"];

// доставка в Екатеринбург — 28 заказов
orders.count_documents(make_document(kvp("delivery.city", "Екатеринбург")));

// оплаченные заказы
orders.count_documents(make_document(kvp("payment.paid", true)));
Go · mongo-driverвложенное поле
orders := client.Database("shop").Collection("orders")

// доставка в Екатеринбург — 28 заказов
orders.CountDocuments(ctx, bson.D{{Key: "delivery.city", Value: "Екатеринбург"}})

// оплаченные заказы
orders.CountDocuments(ctx, bson.D{{Key: "payment.paid", Value: true}})
Ruby · mongoвложенное поле
orders = client.use("shop").database[:orders]

# доставка в Екатеринбург — 28 заказов
orders.count_documents({ "delivery.city" => "Екатеринбург" })

# оплаченные заказы
orders.count_documents({ "payment.paid" => true })
mongosh · оболочкато же самое
db.orders.countDocuments({ "delivery.city": "Екатеринбург" })

db.orders.countDocuments({ "payment.paid": true })
Осторожно: точка — не то же, что вложенный документ

Фильтр {"delivery": {"city": "Екатеринбург"}} — это не то же самое, что {"delivery.city": "Екатеринбург"}. Первый ищет документы, у которых delivery в точности равен {"city": "Екатеринбург"} — то есть содержит ровно одно поле и ничего больше. У наших заказов в delivery три поля, поэтому такой фильтр не найдёт ничего. Точечная нотация проверяет одно поле внутри и на остальные не смотрит.

Точка работает и с массивами — но там правила свои: условие проверяется на каждом элементе, и достаточно одного совпадения. Массивам посвящена глава 2.6.

§7Тот же запрос в Compass

Строка Filter в Compass принимает тот же документ, что и find в коде: запрос проверяют в Compass, а затем переносят в программу без переписывания.

Фильтр по категории смартфоны в MongoDB Compass, найдено 4 документа
Compass · shop.products, фильтр { category: "смартфоны" } — счётчик показывает 1–4 из 4

Счётчик над списком документов показывает, сколько документов вернул фильтр. Это число и записывают в отчёт.

§8Пустой результат: что проверить

Пустой ответ разбирается по шагам. Почти всегда дело в одном из пяти пунктов.

ПроверкаКак проверитьПризнак
Та ли база и коллекцияcol.count_documents({})Ноль на пустом фильтре — вы не в той коллекции или имя с опечаткой
Тот ли тип значения{"price": 18990} против {"price": "18990"}Число не равно строке. Тип виден в Compass рядом с полем
Тот ли регистр и написание"Смартфоны" против "смартфоны"Сравнение строк точное, регистр важен
Есть ли лишние пробелы{"city": "Екатеринбург "}Пробел в конце — тоже символ. Часто приезжает из выгрузок
Не вложенный ли документ целиком{"delivery": {...}} вместо {"delivery.city": ...}Полное сравнение вложенного документа требует точного совпадения всех полей

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

Что написаноЧто происходитКак правильно
find(...)[0]TypeError: курсор не индексируется как списокfind_one(...) или list(find(...))[0]
len(cursor)TypeError: object of type 'Cursor' has no len()count_documents(filter)
Перебор курсора дваждыВторой раз ноль строк, ошибки нетСохранить в список: items = list(...)
find_one(...)["title"] без проверкиTypeError: 'NoneType' object is not subscriptableПроверить на None до обращения к полям
Опора на порядок без sortРаботает, пока однажды не переставитсяВсегда указывать сортировку, если порядок важен
Что написаноЧто происходитКак правильно
Разыменование результата find_oneПустой optional: неопределённое поведение вместо понятной ошибкиif (one) one->view()["title"]
Повторный обход курсораВторой for не даёт ни одного документаСложить документы в std::vector, если нужны дважды
cursor.begin() вызван дваждыВторой вызов возвращает конец: курсор уже прочитанОбходить один раз или собирать результат
get_string() без проверки типаrequested type does not match the underlying typeСверяться с element.type()
Опора на порядок без sortРаботает, пока однажды не переставитсяВсегда указывать сортировку, если порядок важен
Что написаноЧто происходитКак правильно
Ошибка Decode не проверенаВ структуре нули, программа считает это даннымиСравнить с mongo.ErrNoDocuments: это «не нашлось», а не сбой
Повторный All по курсоруПустой срез и nil вместо ошибки — молчаливая потеря данныхЧитать курсор один раз, результат держать в срезе
cursor.Err() не проверен после циклаОбрыв соединения выглядит как конец данныхПосле for cursor.Next(ctx) проверять cursor.Err()
Забыт defer cursor.Close(ctx)Курсор остаётся открытым на сервереЗакрывать курсор или использовать All
Опора на порядок без SetSortРаботает, пока однажды не переставитсяВсегда указывать сортировку, если порядок важен
Что написаноЧто происходитКак правильно
find(...).first["title"] без проверкиNoMethodError: undefined method '[]' for nil:NilClassПроверить на nil до обращения к полям
view.count вместо счёта на сервереДокументы приезжают на клиент ради одного числаcount_documents(filter)
Обход view в циклеКаждый обход — новый запрос к серверуto_a один раз, если данные нужны несколько раз
Символьные ключи в фильтре{ category: "…" } не совпадает со строковым ключом в документеКлючи фильтра пишут строками
Опора на порядок без sortРаботает, пока однажды не переставитсяВсегда указывать сортировку, если порядок важен

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

Чем find отличается от find_one?

find_one возвращает один документ (словарь) или None. find возвращает курсор по всем подходящим документам; чтобы получить сами документы, курсор перебирают или превращают в список.

Почему второй перебор курсора не вывел ничего, хотя ошибки не было?

Курсор одноразовый: он помнит позицию и после полного прохода исчерпан. Данные для повторного использования сохраняют в список.

Смартфоны вернулись в порядке p-006, p-007, p-009, p-008. Это ошибка?

Нет. Без sort порядок не определён: он зависит от того, как документы лежат на диске. Нужен определённый порядок — задайте его явно.

Чем {"delivery.city": "Екатеринбург"} отличается от {"delivery": {"city": "Екатеринбург"}}?

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

Как узнать число подходящих документов, не вытягивая их на клиент?

col.count_documents(filter) — считает сервер, возвращается одно число. len(list(col.find(filter))) даст тот же ответ, но сначала перекачает все документы.

§11Шпаргалка

Один документ col.find_one({"_id": "p-003"}) # вернёт None, если нет
Много документов for d in col.find({...}): print(d["title"]) items = list(col.find({...}))
Вложенное поле {"delivery.city": "Москва"} {"payment.paid": True}
Сколько нашлось col.count_documents({}) col.count_documents( {"category": "ноутбуки"})
Один документ auto one = col.find_one( make_document(kvp("_id", "p-003"))); // пустой optional, если нет
Много документов for (const auto& d : col.find(filter)) std::cout << d["title"].get_string().value;
Вложенное поле kvp("delivery.city", "Москва") kvp("payment.paid", true)
Сколько нашлось col.count_documents(make_document()) col.count_documents( make_document(kvp("category", "ноутбуки")))
Один документ err := col.FindOne(ctx, filter).Decode(&one) // mongo.ErrNoDocuments, если нет
Много документов cursor, _ := col.Find(ctx, filter) var items []Product cursor.All(ctx, &items)
Вложенное поле bson.D{{Key: "delivery.city", Value: "Москва"}}
Сколько нашлось col.CountDocuments(ctx, bson.D{}) col.CountDocuments(ctx, bson.D{{Key: "category", Value: "ноутбуки"}})
Один документ col.find({ "_id" => "p-003" }).first # вернёт nil, если нет
Много документов col.find({ ... }).each { |d| puts d["title"] } items = col.find({ ... }).to_a
Вложенное поле { "delivery.city" => "Москва" } { "payment.paid" => true }
Сколько нашлось col.count_documents({}) col.count_documents({ "category" => "ноутбуки" })

Итог главы

find_one отдаёт один документ или None, find — курсор, который подтягивает документы порциями и проходится ровно один раз. Фильтр — это документ: перечисленные поля соединяются по «и», а вложенные поля адресуются через точку. Порядок документов без явной сортировки не гарантирован. Дальше научимся забирать не весь документ целиком, а только нужные поля.

Глава 1.4 · Чтение: find и курсор · Справочник по MongoDB Автор: Макаров Максим Николаевич