Модуль 2 · Язык фильтров

Справочник по MongoDB · модуль 2 · язык фильтров

2.4

2.4

Логика:
$and $or $not $nor

До сих пор все условия фильтра соединялись по «и». Здесь разобраны логические операторы: $or — «хотя бы одно из условий», $and — «и», записанное явно, $not — отрицание условия и $nor — «ни одно из условий». Отдельно — как они сочетаются с обычными парами фильтра и какой оператор выбрать для той или иной задачи.

Категория
Язык запросов
Уровень
Начальный
Связанные темы
Операторы сравнения, списки значений $in, наличие поля $exists
Зачем это знать
Записывать условия отбора, в которых есть «или» и «не»

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

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

§1$or: хотя бы одно условие

Оператор $or принимает массив условий и отбирает документы, для которых выполнено хотя бы одно из них. Каждое условие в массиве — отдельный документ фильтра, такой же, как в главах 2.1–2.3. В отличие от $in, условия $or могут относиться к разным полям.

Оператор стоит на месте имени поля: {"$or": [{"city": "Ярославль"}, {"ready_to_move": true}]}. Этот фильтр отбирает соискателей, которых можно пригласить в ярославский офис: они либо живут в Ярославле, либо готовы переехать. Пример считает каждое условие отдельно, затем их сочетание, и выводит найденные резюме.

Python · PyMongoиз Ярославля или готов к переезду
print("из Ярославля:      ", resumes.count_documents({"city": "Ярославль"}))
print("готов к переезду:  ", resumes.count_documents({"ready_to_move": True}))
print("одно из двух ($or):", resumes.count_documents({"$or": [{"city": "Ярославль"}, {"ready_to_move": True}]}))

for doc in resumes.find({"$or": [{"city": "Ярославль"}, {"ready_to_move": True}]}, {"_id": 0, "fio": 1, "city": 1, "ready_to_move": 1}):
    print(doc)
C++ · mongocxxиз Ярославля или готов к переезду
std::cout << "из Ярославля:       " << resumes.count_documents(make_document(kvp("city", "Ярославль"))) << std::endl;
std::cout << "готов к переезду:   " << resumes.count_documents(make_document(kvp("ready_to_move", true))) << std::endl;
std::cout << "одно из двух ($or): " << resumes.count_documents(make_document(kvp("$or", make_array(make_document(kvp("city", "Ярославль")), make_document(kvp("ready_to_move", true)))))) << std::endl;

mongocxx::options::find options;
options.projection(make_document(kvp("_id", 0), kvp("fio", 1), kvp("city", 1), kvp("ready_to_move", 1)));

for (const auto& doc : resumes.find(make_document(kvp("$or", make_array(make_document(kvp("city", "Ярославль")), make_document(kvp("ready_to_move", true))))), options)) {
    std::cout << bsoncxx::to_json(doc, bsoncxx::ExtendedJsonMode::k_relaxed) << std::endl;
}
Go · mongo-driverиз Ярославля или готов к переезду
yar, _ := resumes.CountDocuments(ctx, bson.D{{Key: "city", Value: "Ярославль"}})
pereezd, _ := resumes.CountDocuments(ctx, bson.D{{Key: "ready_to_move", Value: true}})
ili, _ := resumes.CountDocuments(ctx, bson.D{{Key: "$or", Value: bson.A{bson.D{{Key: "city", Value: "Ярославль"}}, bson.D{{Key: "ready_to_move", Value: true}}}}})

fmt.Println("из Ярославля:      ", yar)
fmt.Println("готов к переезду:  ", pereezd)
fmt.Println("одно из двух ($or):", ili)

cursor, _ := resumes.Find(ctx, bson.D{{Key: "$or", Value: bson.A{bson.D{{Key: "city", Value: "Ярославль"}}, bson.D{{Key: "ready_to_move", Value: true}}}}},
    options.Find().SetProjection(bson.D{{Key: "_id", Value: 0}, {Key: "fio", Value: 1}, {Key: "city", Value: 1}, {Key: "ready_to_move", Value: 1}}))
for cursor.Next(ctx) {
    var doc bson.D
    cursor.Decode(&doc)
    out, _ := bson.MarshalExtJSON(doc, false, false)
    fmt.Println(string(out))
}
Ruby · mongoиз Ярославля или готов к переезду
puts "из Ярославля:       " + resumes.count_documents({ "city" => "Ярославль" }).to_s
puts "готов к переезду:   " + resumes.count_documents({ "ready_to_move" => true }).to_s
puts "одно из двух ($or): " + resumes.count_documents({ "$or" => [{ "city" => "Ярославль" }, { "ready_to_move" => true }] }).to_s

resumes.find({ "$or" => [{ "city" => "Ярославль" }, { "ready_to_move" => true }] }, projection: { "_id" => 0, "fio" => 1, "city" => 1, "ready_to_move" => 1 })
       .each { |doc| puts doc.inspect }
Вывод в терминалрезультат запуска
из Ярославля:       5
готов к переезду:   4
одно из двух ($or): 8
{'fio': 'Анна Белова', 'city': 'Ярославль'}
{'fio': 'Пётр Ковалёв', 'city': 'Ярославль', 'ready_to_move': False}
{'fio': 'Алина Дроздова', 'city': 'Москва', 'ready_to_move': True}
{'fio': 'Игорь Самойлов', 'city': 'Ярославль', 'ready_to_move': False}
{'fio': 'Дарья Нечаева', 'city': 'Санкт-Петербург', 'ready_to_move': True}
{'fio': 'Ксения Лапина', 'city': 'Ярославль', 'ready_to_move': True}
{'fio': 'Марк Ефремов', 'city': 'Новосибирск', 'ready_to_move': True}
{'fio': 'Ольга Пирогова', 'city': 'Ярославль', 'ready_to_move': False}
Вывод в терминалрезультат запуска
из Ярославля:       5
готов к переезду:   4
одно из двух ($or): 8
{ "fio" : "Анна Белова", "city" : "Ярославль" }
{ "fio" : "Пётр Ковалёв", "city" : "Ярославль", "ready_to_move" : false }
{ "fio" : "Алина Дроздова", "city" : "Москва", "ready_to_move" : true }
{ "fio" : "Игорь Самойлов", "city" : "Ярославль", "ready_to_move" : false }
{ "fio" : "Дарья Нечаева", "city" : "Санкт-Петербург", "ready_to_move" : true }
{ "fio" : "Ксения Лапина", "city" : "Ярославль", "ready_to_move" : true }
{ "fio" : "Марк Ефремов", "city" : "Новосибирск", "ready_to_move" : true }
{ "fio" : "Ольга Пирогова", "city" : "Ярославль", "ready_to_move" : false }
Вывод в терминалрезультат запуска
из Ярославля:       5
готов к переезду:   4
одно из двух ($or): 8
{"fio":"Анна Белова","city":"Ярославль"}
{"fio":"Пётр Ковалёв","city":"Ярославль","ready_to_move":false}
{"fio":"Алина Дроздова","city":"Москва","ready_to_move":true}
{"fio":"Игорь Самойлов","city":"Ярославль","ready_to_move":false}
{"fio":"Дарья Нечаева","city":"Санкт-Петербург","ready_to_move":true}
{"fio":"Ксения Лапина","city":"Ярославль","ready_to_move":true}
{"fio":"Марк Ефремов","city":"Новосибирск","ready_to_move":true}
{"fio":"Ольга Пирогова","city":"Ярославль","ready_to_move":false}
Вывод в терминалрезультат запуска
из Ярославля:       5
готов к переезду:   4
одно из двух ($or): 8
{"fio"=>"Анна Белова", "city"=>"Ярославль"}
{"fio"=>"Пётр Ковалёв", "city"=>"Ярославль", "ready_to_move"=>false}
{"fio"=>"Алина Дроздова", "city"=>"Москва", "ready_to_move"=>true}
{"fio"=>"Игорь Самойлов", "city"=>"Ярославль", "ready_to_move"=>false}
{"fio"=>"Дарья Нечаева", "city"=>"Санкт-Петербург", "ready_to_move"=>true}
{"fio"=>"Ксения Лапина", "city"=>"Ярославль", "ready_to_move"=>true}
{"fio"=>"Марк Ефремов", "city"=>"Новосибирск", "ready_to_move"=>true}
{"fio"=>"Ольга Пирогова", "city"=>"Ярославль", "ready_to_move"=>false}

Из Ярославля пять резюме, готовы к переезду четыре, а через $or найдено восемь, не девять. Ксения Лапина живёт в Ярославле и готова к переезду, то есть подходит под оба условия, но в результат попала один раз. $or проверяет каждый документ один раз, и документ либо входит в результат, либо нет. Не подошёл только Тимур Валиев — он из Казани и переезжать не готов.

Compass, коллекция hh.resumes: фильтр с $or по городу и готовности к переезду, счётчик 1 – 8 of 8
Compass · hh.resumes, фильтр { $or: [{ city: "Ярославль" }, { ready_to_move: true }] } — восемь резюме из девяти

Вкладка Documents подписана числом 9, счётчик — 1 – 8 of 8. У первых двух документов в кадре город — Ярославль, у третьего, Алины Дроздовой, — Москва, но поле ready_to_move равно true. Каждый документ прошёл хотя бы по одному условию.

§2$or на одном поле и $in

Если все условия $or проверяют одно поле на равенство, тот же отбор записывается через $in из главы 2.3.

Python · PyMongo$or на одном поле и $in
print("$or:", vacancies.count_documents({"$or": [{"city": "Москва"}, {"city": "Казань"}]}))
print("$in:", vacancies.count_documents({"city": {"$in": ["Москва", "Казань"]}}))
C++ · mongocxx$or на одном поле и $in
std::cout << "$or: " << vacancies.count_documents(make_document(kvp("$or", make_array(make_document(kvp("city", "Москва")), make_document(kvp("city", "Казань")))))) << std::endl;
std::cout << "$in: " << vacancies.count_documents(make_document(kvp("city", make_document(kvp("$in", make_array("Москва", "Казань")))))) << std::endl;
Go · mongo-driver$or на одном поле и $in
cherezOr, _ := vacancies.CountDocuments(ctx, bson.D{{Key: "$or", Value: bson.A{bson.D{{Key: "city", Value: "Москва"}}, bson.D{{Key: "city", Value: "Казань"}}}}})
cherezIn, _ := vacancies.CountDocuments(ctx, bson.D{{Key: "city", Value: bson.D{{Key: "$in", Value: bson.A{"Москва", "Казань"}}}}})

fmt.Println("$or:", cherezOr)
fmt.Println("$in:", cherezIn)
Ruby · mongo$or на одном поле и $in
puts "$or: " + vacancies.count_documents({ "$or" => [{ "city" => "Москва" }, { "city" => "Казань" }] }).to_s
puts "$in: " + vacancies.count_documents({ "city" => { "$in" => ["Москва", "Казань"] } }).to_s
Вывод в терминалрезультат запуска
$or: 4
$in: 4
Вывод в терминалрезультат запуска
$or: 4
$in: 4
Вывод в терминалрезультат запуска
$or: 4
$in: 4
Вывод в терминалрезультат запуска
$or: 4
$in: 4

Результаты совпадают. Запись через $in короче: имя поля стоит один раз, значения перечислены в одном списке. $or нужен, когда условия относятся к разным полям или используют разные операторы.

Compass, коллекция hh.vacancies: фильтр $or с двумя городами, счётчик 1 – 4 of 4
Compass · hh.vacancies, фильтр { $or: [{ city: "Москва" }, { city: "Казань" }] }

Те же четыре вакансии и в том же порядке, что по фильтру с $in в главе 2.3: одна в Казани и три в Москве. Проекция оставляет название и город.

§3«И» вместе с «или»

Оператор $or — одна из пар фильтра, и с остальными парами он соединяется по «и». Фильтр {"salary": {"$lte": 90000}, "$or": [...]} читается так: зарплата не больше 90 000, и при этом выполнено хотя бы одно из условий $or.

Python · PyMongoусловие на зарплату и «или»
filtr = {"salary": {"$lte": 90000},
         "$or": [{"city": "Ярославль"},
                 {"ready_to_move": True}]}

for doc in resumes.find(filtr, {"_id": 0, "fio": 1, "salary": 1, "city": 1, "ready_to_move": 1}):
    print(doc)
C++ · mongocxxусловие на зарплату и «или»
auto filtr = make_document(
    kvp("salary", make_document(kvp("$lte", 90000))),
    kvp("$or", make_array(make_document(kvp("city", "Ярославль")), make_document(kvp("ready_to_move", true)))));

mongocxx::options::find options;
options.projection(make_document(kvp("_id", 0), kvp("fio", 1), kvp("salary", 1), kvp("city", 1), kvp("ready_to_move", 1)));

for (const auto& doc : resumes.find(filtr.view(), options)) {
    std::cout << bsoncxx::to_json(doc, bsoncxx::ExtendedJsonMode::k_relaxed) << std::endl;
}
Go · mongo-driverусловие на зарплату и «или»
filtr := bson.D{
    {Key: "salary", Value: bson.D{{Key: "$lte", Value: 90000}}},
    {Key: "$or", Value: bson.A{bson.D{{Key: "city", Value: "Ярославль"}}, bson.D{{Key: "ready_to_move", Value: true}}}}}

cursor, _ := resumes.Find(ctx, filtr,
    options.Find().SetProjection(bson.D{{Key: "_id", Value: 0}, {Key: "fio", Value: 1}, {Key: "salary", Value: 1}, {Key: "city", Value: 1}, {Key: "ready_to_move", Value: 1}}))
for cursor.Next(ctx) {
    var doc bson.D
    cursor.Decode(&doc)
    out, _ := bson.MarshalExtJSON(doc, false, false)
    fmt.Println(string(out))
}
Ruby · mongoусловие на зарплату и «или»
filtr = { "salary" => { "$lte" => 90000 },
          "$or" => [{ "city" => "Ярославль" },
                    { "ready_to_move" => true }] }

resumes.find(filtr, projection: { "_id" => 0, "fio" => 1, "salary" => 1, "city" => 1, "ready_to_move" => 1 })
       .each { |doc| puts doc.inspect }
Вывод в терминалрезультат запуска
{'fio': 'Анна Белова', 'city': 'Ярославль', 'salary': 65000}
{'fio': 'Пётр Ковалёв', 'city': 'Ярославль', 'salary': 70000, 'ready_to_move': False}
{'fio': 'Дарья Нечаева', 'city': 'Санкт-Петербург', 'salary': 80000, 'ready_to_move': True}
{'fio': 'Ксения Лапина', 'city': 'Ярославль', 'salary': 90000, 'ready_to_move': True}
{'fio': 'Марк Ефремов', 'city': 'Новосибирск', 'salary': 75000, 'ready_to_move': True}
Вывод в терминалрезультат запуска
{ "fio" : "Анна Белова", "city" : "Ярославль", "salary" : 65000 }
{ "fio" : "Пётр Ковалёв", "city" : "Ярославль", "salary" : 70000, "ready_to_move" : false }
{ "fio" : "Дарья Нечаева", "city" : "Санкт-Петербург", "salary" : 80000, "ready_to_move" : true }
{ "fio" : "Ксения Лапина", "city" : "Ярославль", "salary" : 90000, "ready_to_move" : true }
{ "fio" : "Марк Ефремов", "city" : "Новосибирск", "salary" : 75000, "ready_to_move" : true }
Вывод в терминалрезультат запуска
{"fio":"Анна Белова","city":"Ярославль","salary":65000}
{"fio":"Пётр Ковалёв","city":"Ярославль","salary":70000,"ready_to_move":false}
{"fio":"Дарья Нечаева","city":"Санкт-Петербург","salary":80000,"ready_to_move":true}
{"fio":"Ксения Лапина","city":"Ярославль","salary":90000,"ready_to_move":true}
{"fio":"Марк Ефремов","city":"Новосибирск","salary":75000,"ready_to_move":true}
Вывод в терминалрезультат запуска
{"fio"=>"Анна Белова", "city"=>"Ярославль", "salary"=>65000}
{"fio"=>"Пётр Ковалёв", "city"=>"Ярославль", "salary"=>70000, "ready_to_move"=>false}
{"fio"=>"Дарья Нечаева", "city"=>"Санкт-Петербург", "salary"=>80000, "ready_to_move"=>true}
{"fio"=>"Ксения Лапина", "city"=>"Ярославль", "salary"=>90000, "ready_to_move"=>true}
{"fio"=>"Марк Ефремов", "city"=>"Новосибирск", "salary"=>75000, "ready_to_move"=>true}

Из восьми резюме, найденных в §1, остались пять: у Игоря Самойлова, Алины Дроздовой и Ольги Пироговой ожидания выше 90 000. Условие на зарплату проверяется у каждого документа, а $or даёт выбор только между своими условиями.

Compass, коллекция hh.resumes: фильтр salary не больше 90000 и $or, счётчик 1 – 5 of 5
Compass · hh.resumes, условие на зарплату и $or в одном фильтре

Фильтр в поле Compass длиннее строки и обрезан справа; начало видно: { salary: { $lte: 90000 }, $or: [...]. У всех пяти документов зарплата не больше 90 000, и каждый либо из Ярославля, либо с ready_to_move: true.

§4$and: «и», записанное явно

Оператор $and принимает массив условий и требует, чтобы выполнились все. Обычно он не нужен: пары фильтра и так соединяются по «и». Явная запись требуется, когда два условия нельзя поставить в один документ, потому что у них одинаковый ключ. Чаще всего это два $or.

Задача: найти соискателей, которых можно пригласить в Ярославль (§1), и при этом с квалификацией — высшим образованием или опытом работы от 10 месяцев. Каждое требование — отдельное «или». Условия сначала сохраняются в переменные, а затем из них собирается фильтр: так видно, из каких частей он состоит.

Python · PyMongoдва условия «или» сразу
dostupen = {"$or": [{"city": "Ярославль"}, {"ready_to_move": True}]}
kvalif = {"$or": [{"education.level": "Высшее"}, {"experience.months": {"$gte": 10}}]}

print("доступен для Ярославля:", resumes.count_documents(dostupen))
print("есть квалификация:     ", resumes.count_documents(kvalif))

for doc in resumes.find({"$and": [dostupen, kvalif]}, {"_id": 0, "fio": 1}):
    print(doc)
C++ · mongocxxдва условия «или» сразу
auto dostupen = make_document(kvp("$or", make_array(
    make_document(kvp("city", "Ярославль")),
    make_document(kvp("ready_to_move", true)))));
auto kvalif = make_document(kvp("$or", make_array(
    make_document(kvp("education.level", "Высшее")),
    make_document(kvp("experience.months", make_document(kvp("$gte", 10)))))));

std::cout << "доступен для Ярославля: " << resumes.count_documents(dostupen.view()) << std::endl;
std::cout << "есть квалификация:      " << resumes.count_documents(kvalif.view()) << std::endl;

mongocxx::options::find options;
options.projection(make_document(kvp("_id", 0), kvp("fio", 1)));

for (const auto& doc : resumes.find(make_document(kvp("$and", make_array(dostupen.view(), kvalif.view()))), options)) {
    std::cout << bsoncxx::to_json(doc, bsoncxx::ExtendedJsonMode::k_relaxed) << std::endl;
}
Go · mongo-driverдва условия «или» сразу
dostupen := bson.D{{Key: "$or", Value: bson.A{
    bson.D{{Key: "city", Value: "Ярославль"}},
    bson.D{{Key: "ready_to_move", Value: true}}}}}
kvalif := bson.D{{Key: "$or", Value: bson.A{
    bson.D{{Key: "education.level", Value: "Высшее"}},
    bson.D{{Key: "experience.months", Value: bson.D{{Key: "$gte", Value: 10}}}}}}}

nDostupen, _ := resumes.CountDocuments(ctx, dostupen)
nKvalif, _ := resumes.CountDocuments(ctx, kvalif)

fmt.Println("доступен для Ярославля:", nDostupen)
fmt.Println("есть квалификация:     ", nKvalif)

cursor, _ := resumes.Find(ctx, bson.D{{Key: "$and", Value: bson.A{dostupen, kvalif}}},
    options.Find().SetProjection(bson.D{{Key: "_id", Value: 0}, {Key: "fio", Value: 1}}))
for cursor.Next(ctx) {
    var doc bson.D
    cursor.Decode(&doc)
    out, _ := bson.MarshalExtJSON(doc, false, false)
    fmt.Println(string(out))
}
Ruby · mongoдва условия «или» сразу
dostupen = { "$or" => [{ "city" => "Ярославль" }, { "ready_to_move" => true }] }
kvalif = { "$or" => [{ "education.level" => "Высшее" },
                     { "experience.months" => { "$gte" => 10 } }] }

puts "доступен для Ярославля: " + resumes.count_documents(dostupen).to_s
puts "есть квалификация:      " + resumes.count_documents(kvalif).to_s

resumes.find({ "$and" => [dostupen, kvalif] }, projection: { "_id" => 0, "fio" => 1 })
       .each { |doc| puts doc.inspect }
Вывод в терминалрезультат запуска
доступен для Ярославля: 8
есть квалификация:      5
{'fio': 'Алина Дроздова'}
{'fio': 'Игорь Самойлов'}
{'fio': 'Марк Ефремов'}
{'fio': 'Ольга Пирогова'}
Вывод в терминалрезультат запуска
доступен для Ярославля: 8
есть квалификация:      5
{ "fio" : "Алина Дроздова" }
{ "fio" : "Игорь Самойлов" }
{ "fio" : "Марк Ефремов" }
{ "fio" : "Ольга Пирогова" }
Вывод в терминалрезультат запуска
доступен для Ярославля: 8
есть квалификация:      5
{"fio":"Алина Дроздова"}
{"fio":"Игорь Самойлов"}
{"fio":"Марк Ефремов"}
{"fio":"Ольга Пирогова"}
Вывод в терминалрезультат запуска
доступен для Ярославля: 8
есть квалификация:      5
{"fio"=>"Алина Дроздова"}
{"fio"=>"Игорь Самойлов"}
{"fio"=>"Марк Ефремов"}
{"fio"=>"Ольга Пирогова"}

Первому условию отвечают восемь резюме, второму — пять, обоим сразу — четыре. Тимур Валиев квалификацией подходит, но он из Казани и переезжать не готов. Марк Ефремов прошёл по второму условию благодаря опыту: образование у него среднее профессиональное, зато 11 месяцев работы.

Compass, коллекция hh.resumes: фильтр $and из двух $or, счётчик 1 – 4 of 4
Compass · hh.resumes, $and с двумя условиями $or — четыре резюме

В Compass этот фильтр вводится одной строкой и в поле целиком не помещается: видны $and и начало первого $or. Счётчик 1 – 4 of 4 совпадает с выводом программы. Первое резюме в списке — Алины Дроздовой: она подошла по готовности к переезду и по высшему образованию.

Без $and два $or пришлось бы записать двумя парами с одним ключом. Словарь Python и хеш Ruby оставили бы только последнюю пару, и фильтр проверял бы одну квалификацию — нашлось бы пять резюме, включая резюме Тимура Валиева. Внутри массива $and повторов ключей нет: каждое условие лежит в своём документе.

§5$not: отрицание условия

Оператор $not ставится на месте условия поля и принимает другое условие — документ с оператором. Фильтр {"updated": {"$not": {"$gte": дата}}} отбирает документы, для которых условие «обновлено не раньше даты» не выполняется. Значение без оператора $not не принимает: для «не равно» есть $ne.

Как и $ne, оператор $not возвращает документы, где поля нет: для них внутреннее условие не выполнено. Пример сравнивает $not с прямым условием «раньше даты».

Python · PyMongoотрицание условия
from datetime import datetime

sep01 = datetime(2026, 9, 1)

print("$not $gte 01.09:", resumes.count_documents({"updated": {"$not": {"$gte": sep01}}}))
print("$lt 01.09:      ", resumes.count_documents({"updated": {"$lt": sep01}}))

for doc in resumes.find({"updated": {"$not": {"$gte": sep01}}}, {"_id": 0, "fio": 1, "updated": 1}):
    print(doc)
C++ · mongocxxотрицание условия
auto sep01 = bsoncxx::types::b_date{std::chrono::milliseconds{1788220800000}};   // 01.09.2026 00:00 UTC

std::cout << "$not $gte 01.09: " << resumes.count_documents(make_document(kvp("updated", make_document(kvp("$not", make_document(kvp("$gte", sep01))))))) << std::endl;
std::cout << "$lt 01.09:       " << resumes.count_documents(make_document(kvp("updated", make_document(kvp("$lt", sep01))))) << std::endl;

mongocxx::options::find options;
options.projection(make_document(kvp("_id", 0), kvp("fio", 1), kvp("updated", 1)));

for (const auto& doc : resumes.find(make_document(kvp("updated", make_document(kvp("$not", make_document(kvp("$gte", sep01)))))), options)) {
    std::cout << bsoncxx::to_json(doc, bsoncxx::ExtendedJsonMode::k_relaxed) << std::endl;
}
Go · mongo-driverотрицание условия
sep01 := time.Date(2026, 9, 1, 0, 0, 0, 0, time.UTC)

neSentyabr, _ := resumes.CountDocuments(ctx, bson.D{{Key: "updated", Value: bson.D{{Key: "$not", Value: bson.D{{Key: "$gte", Value: sep01}}}}}})
doSentyabrya, _ := resumes.CountDocuments(ctx, bson.D{{Key: "updated", Value: bson.D{{Key: "$lt", Value: sep01}}}})

fmt.Println("$not $gte 01.09:", neSentyabr)
fmt.Println("$lt 01.09:      ", doSentyabrya)

cursor, _ := resumes.Find(ctx, bson.D{{Key: "updated", Value: bson.D{{Key: "$not", Value: bson.D{{Key: "$gte", Value: sep01}}}}}},
    options.Find().SetProjection(bson.D{{Key: "_id", Value: 0}, {Key: "fio", Value: 1}, {Key: "updated", Value: 1}}))
for cursor.Next(ctx) {
    var doc bson.D
    cursor.Decode(&doc)
    out, _ := bson.MarshalExtJSON(doc, false, false)
    fmt.Println(string(out))
}
Ruby · mongoотрицание условия
sep01 = Time.utc(2026, 9, 1)

puts "$not $gte 01.09: " + resumes.count_documents({ "updated" => { "$not" => { "$gte" => sep01 } } }).to_s
puts "$lt 01.09:       " + resumes.count_documents({ "updated" => { "$lt" => sep01 } }).to_s

resumes.find({ "updated" => { "$not" => { "$gte" => sep01 } } }, projection: { "_id" => 0, "fio" => 1, "updated" => 1 })
       .each { |doc| puts doc.inspect }
Вывод в терминалрезультат запуска
$not $gte 01.09: 4
$lt 01.09:       3
{'fio': 'Анна Белова'}
{'fio': 'Игорь Самойлов', 'updated': datetime.datetime(2026, 8, 28, 7, 5)}
{'fio': 'Тимур Валиев', 'updated': datetime.datetime(2026, 7, 19, 11, 30)}
{'fio': 'Марк Ефремов', 'updated': datetime.datetime(2026, 8, 15, 16, 3)}
Вывод в терминалрезультат запуска
$not $gte 01.09: 4
$lt 01.09:       3
{ "fio" : "Анна Белова" }
{ "fio" : "Игорь Самойлов", "updated" : { "$date" : "2026-08-28T07:05:00Z" } }
{ "fio" : "Тимур Валиев", "updated" : { "$date" : "2026-07-19T11:30:00Z" } }
{ "fio" : "Марк Ефремов", "updated" : { "$date" : "2026-08-15T16:03:00Z" } }
Вывод в терминалрезультат запуска
$not $gte 01.09: 4
$lt 01.09:       3
{"fio":"Анна Белова"}
{"fio":"Игорь Самойлов","updated":{"$date":"2026-08-28T07:05:00Z"}}
{"fio":"Тимур Валиев","updated":{"$date":"2026-07-19T11:30:00Z"}}
{"fio":"Марк Ефремов","updated":{"$date":"2026-08-15T16:03:00Z"}}
Вывод в терминалрезультат запуска
$not $gte 01.09: 4
$lt 01.09:       3
{"fio"=>"Анна Белова"}
{"fio"=>"Игорь Самойлов", "updated"=>2026-08-28 07:05:00 UTC}
{"fio"=>"Тимур Валиев", "updated"=>2026-07-19 11:30:00 UTC}
{"fio"=>"Марк Ефремов", "updated"=>2026-08-15 16:03:00 UTC}

Отрицание нашло четыре резюме, условие $lt — три. Четвёртое — резюме Анны Беловой без поля updated: в выводе у него только ФИО. Если нужны резюме, обновлённые до сентября, условие записывают прямо, через $lt. $not нужен, когда внутреннее условие сложно записать наоборот, — например, с текстовым шаблоном из главы 2.7.

Compass, коллекция hh.resumes: фильтр updated $not $gte ISODate 2026-09-01, счётчик 1 – 4 of 4, у первого резюме нет поля updated
Compass · hh.resumes, фильтр { updated: { $not: { $gte: ISODate("2026-09-01") } } }

У трёх документов раскрыто поле updated — даты в июле и августе. У первого, Анны Беловой, поля нет: отрицание вернуло документ, который прямое условие $lt пропустило бы.

§6$nor: ни одно из условий

Оператор $nor — от not or — принимает массив условий, как $or, и отбирает документы, для которых не выполнено ни одно из них. Пример ищет соискателей не из Москвы, которые при этом не отметили готовность к переезду.

Python · PyMongoни одно из условий
filtr = {"$nor": [{"city": "Москва"}, {"ready_to_move": True}]}

for doc in resumes.find(filtr, {"_id": 0, "fio": 1, "city": 1, "ready_to_move": 1}):
    print(doc)
C++ · mongocxxни одно из условий
auto filtr = make_document(kvp("$nor", make_array(
    make_document(kvp("city", "Москва")),
    make_document(kvp("ready_to_move", true)))));

mongocxx::options::find options;
options.projection(make_document(kvp("_id", 0), kvp("fio", 1), kvp("city", 1), kvp("ready_to_move", 1)));

for (const auto& doc : resumes.find(filtr.view(), options)) {
    std::cout << bsoncxx::to_json(doc, bsoncxx::ExtendedJsonMode::k_relaxed) << std::endl;
}
Go · mongo-driverни одно из условий
filtr := bson.D{{Key: "$nor", Value: bson.A{
    bson.D{{Key: "city", Value: "Москва"}},
    bson.D{{Key: "ready_to_move", Value: true}}}}}

cursor, _ := resumes.Find(ctx, filtr,
    options.Find().SetProjection(bson.D{{Key: "_id", Value: 0}, {Key: "fio", Value: 1}, {Key: "city", Value: 1}, {Key: "ready_to_move", Value: 1}}))
for cursor.Next(ctx) {
    var doc bson.D
    cursor.Decode(&doc)
    out, _ := bson.MarshalExtJSON(doc, false, false)
    fmt.Println(string(out))
}
Ruby · mongoни одно из условий
filtr = { "$nor" => [{ "city" => "Москва" }, { "ready_to_move" => true }] }

resumes.find(filtr, projection: { "_id" => 0, "fio" => 1, "city" => 1, "ready_to_move" => 1 })
       .each { |doc| puts doc.inspect }
Вывод в терминалрезультат запуска
{'fio': 'Анна Белова', 'city': 'Ярославль'}
{'fio': 'Пётр Ковалёв', 'city': 'Ярославль', 'ready_to_move': False}
{'fio': 'Игорь Самойлов', 'city': 'Ярославль', 'ready_to_move': False}
{'fio': 'Тимур Валиев', 'city': 'Казань', 'ready_to_move': False}
{'fio': 'Ольга Пирогова', 'city': 'Ярославль', 'ready_to_move': False}
Вывод в терминалрезультат запуска
{ "fio" : "Анна Белова", "city" : "Ярославль" }
{ "fio" : "Пётр Ковалёв", "city" : "Ярославль", "ready_to_move" : false }
{ "fio" : "Игорь Самойлов", "city" : "Ярославль", "ready_to_move" : false }
{ "fio" : "Тимур Валиев", "city" : "Казань", "ready_to_move" : false }
{ "fio" : "Ольга Пирогова", "city" : "Ярославль", "ready_to_move" : false }
Вывод в терминалрезультат запуска
{"fio":"Анна Белова","city":"Ярославль"}
{"fio":"Пётр Ковалёв","city":"Ярославль","ready_to_move":false}
{"fio":"Игорь Самойлов","city":"Ярославль","ready_to_move":false}
{"fio":"Тимур Валиев","city":"Казань","ready_to_move":false}
{"fio":"Ольга Пирогова","city":"Ярославль","ready_to_move":false}
Вывод в терминалрезультат запуска
{"fio"=>"Анна Белова", "city"=>"Ярославль"}
{"fio"=>"Пётр Ковалёв", "city"=>"Ярославль", "ready_to_move"=>false}
{"fio"=>"Игорь Самойлов", "city"=>"Ярославль", "ready_to_move"=>false}
{"fio"=>"Тимур Валиев", "city"=>"Казань", "ready_to_move"=>false}
{"fio"=>"Ольга Пирогова", "city"=>"Ярославль", "ready_to_move"=>false}

Пять резюме. Алина Дроздова не прошла по обоим условиям, Дарья Нечаева, Ксения Лапина и Марк Ефремов — по второму. Резюме Анны Беловой вошло в результат: поля ready_to_move в нём нет, и условие «готов к переезду» для него не выполнено. $nor, как и остальные отрицания, возвращает документы без поля.

Compass, коллекция hh.resumes: фильтр $nor по городу Москва и готовности к переезду, счётчик 1 – 5 of 5
Compass · hh.resumes, фильтр { $nor: [{ city: "Москва" }, { ready_to_move: true }] }

Ни у одного документа нет города «Москва» и нет ready_to_move: true. У первого, Анны Беловой, поля ready_to_move нет совсем, и $nor его вернул.

§7Какой оператор выбрать

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

ЗадачаЗапись
Поле равно одному из значений$in, глава 2.3
Поле не равно значению$ne, глава 2.2
Поле не равно ни одному из значений$nin, глава 2.3
Значение поля в диапазонедва оператора сравнения в одном документе поля, глава 2.2
Условия на разных полях, нужны всепары фильтра через запятую
Условия на разных полях, нужно хотя бы одно$or
Два «или» в одном фильтре$and с двумя $or
Не выполнено ни одно из условий на разных полях$nor

Все отрицания — $ne, $nin, $not, $nor — возвращают документы, в которых проверяемого поля нет. Если такие документы не нужны, к фильтру добавляют условие на наличие поля — оператор $exists из следующей главы.

§8Логика в Compass

Кадры в разделах выше сняты в Compass на тех же запросах, что и примеры. Логические операторы в поле фильтра пишутся так же, как в коде: $or, $and и $nor со списком условий в квадратных скобках, $not — с документом условия. Длинный фильтр вводится одной строкой; Compass показывает его начало, а остальное прокручивается в поле.

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

Что написаноЧто происходитКак правильно
{"$or": [...], "$or": [...]}Работает только второе «или»: в словаре остаётся последняя пара{"$and": [{"$or": [...]}, {"$or": [...]}]}
{"city": {"$not": "Москва"}}Ошибка сервера: $not needs a regex or a document{"$ne": "Москва"}
{"$or": {"city": "Москва"}}Ошибка сервера: $or must be an array{"$or": [{"city": "Москва"}]}
Список для $or собран из формы и оказался пустымОшибка сервера: $and/$or/$nor must be a nonempty arrayПроверить пустой список до запроса и не добавлять условие
{"$or": [{"city": "Москва"}, {"city": "Казань"}]}Работает, но условие на одно поле длиннее, чем нужно{"city": {"$in": ["Москва", "Казань"]}}
{"updated": {"$not": {"$gte": sep01}}}Кроме более ранних дат находит документы без поляЕсли нужны только ранние даты — $lt
Что написаноЧто происходитКак правильно
make_document(kvp("city", make_document(kvp("$not", "Москва"))))Ошибка сервера: $not needs a regex or a documentmake_document(kvp("$ne", "Москва"))
make_document(kvp("$or", make_document(kvp("city", "Москва"))))Ошибка сервера: $or must be an arraymake_document(kvp("$or", make_array(make_document(kvp("city", "Москва")))))
Список для $or собран из формы и оказался пустымОшибка сервера: $and/$or/$nor must be a nonempty arrayПроверить пустой список до запроса и не добавлять условие
make_document(kvp("$or", make_array(make_document(kvp("city", "Москва")), make_document(kvp("city", "Казань")))))Работает, но условие на одно поле длиннее, чем нужноmake_document(kvp("city", make_document(kvp("$in", make_array("Москва", "Казань")))))
make_document(kvp("updated", make_document(kvp("$not", make_document(kvp("$gte", sep01))))))Кроме более ранних дат находит документы без поляЕсли нужны только ранние даты — $lt
Что написаноЧто происходитКак правильно
bson.D{{Key: "city", Value: bson.D{{Key: "$not", Value: "Москва"}}}}Ошибка сервера: $not needs a regex or a documentbson.D{{Key: "$ne", Value: "Москва"}}
bson.D{{Key: "$or", Value: bson.D{{Key: "city", Value: "Москва"}}}}Ошибка сервера: $or must be an arraybson.D{{Key: "$or", Value: bson.A{bson.D{{Key: "city", Value: "Москва"}}}}}
Список для $or собран из формы и оказался пустымОшибка сервера: $and/$or/$nor must be a nonempty arrayПроверить пустой список до запроса и не добавлять условие
bson.D{{Key: "$or", Value: bson.A{bson.D{{Key: "city", Value: "Москва"}}, bson.D{{Key: "city", Value: "Казань"}}}}}Работает, но условие на одно поле длиннее, чем нужноbson.D{{Key: "city", Value: bson.D{{Key: "$in", Value: bson.A{"Москва", "Казань"}}}}}
bson.D{{Key: "updated", Value: bson.D{{Key: "$not", Value: bson.D{{Key: "$gte", Value: sep01}}}}}}Кроме более ранних дат находит документы без поляЕсли нужны только ранние даты — $lt
Что написаноЧто происходитКак правильно
{ "$or" => [...], "$or" => [...] }Работает только второе «или»: в хеше остаётся последняя пара{ "$and" => [{ "$or" => [...] }, { "$or" => [...] }] }
{ "city" => { "$not" => "Москва" } }Ошибка сервера: $not needs a regex or a document{ "$ne" => "Москва" }
{ "$or" => { "city" => "Москва" } }Ошибка сервера: $or must be an array{ "$or" => [{ "city" => "Москва" }] }
Список для $or собран из формы и оказался пустымОшибка сервера: $and/$or/$nor must be a nonempty arrayПроверить пустой список до запроса и не добавлять условие
{ "$or" => [{ "city" => "Москва" }, { "city" => "Казань" }] }Работает, но условие на одно поле длиннее, чем нужно{ "city" => { "$in" => ["Москва", "Казань"] } }
{ "updated" => { "$not" => { "$gte" => sep01 } } }Кроме более ранних дат находит документы без поляЕсли нужны только ранние даты — $lt

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

Из Ярославля пять резюме, готовы к переезду четыре. Почему $or по этим условиям нашёл восемь?

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

Как читается {"salary": {"$lte": 90000}, "$or": [{"city": "Ярославль"}, {"ready_to_move": true}]}?

Зарплата не больше 90 000, и при этом соискатель из Ярославля или готов к переезду. $or — одна из пар фильтра и соединяется с остальными по «и».

Когда $and нужно писать явно?

Когда в фильтре два условия с одним ключом, — чаще всего два $or. В одном документе ключ может стоять только один раз, поэтому условия кладут в массив $and, каждое в свой документ.

Почему {"city": {"$not": "Москва"}} даёт ошибку?

$not принимает условие — документ с оператором или текстовый шаблон. Строку он не принимает. Для «не равно» служит $ne: {"city": {"$ne": "Москва"}}.

Попадёт ли в результат $nor документ, в котором нет проверяемого поля?

Да. Для такого документа условие на это поле не выполнено, и если остальные условия тоже не выполнены, $nor его вернёт. Так же ведут себя $ne, $nin и $not.

§11Шпаргалка

Хотя бы одно условие {"$or": [{"city": "Ярославль"}, {"ready_to_move": True}]}
«И» вместе с «или» {"salary": {"$lte": 90000}, "$or": [{"city": "Ярославль"}, {"city": "Казань"}]}
Два «или» сразу {"$and": [{"$or": [...]}, {"$or": [...]}]}
Отрицание условия {"salary": {"$not": {"$gte": 100000}}}
Ни одно из условий {"$nor": [{"city": "Москва"}, {"ready_to_move": True}]}
Хотя бы одно условие make_document(kvp("$or", make_array(make_document(kvp("city", "Ярославль")), make_document(kvp("ready_to_move", true)))))
«И» вместе с «или» make_document(kvp("salary", make_document(kvp("$lte", 90000))), kvp("$or", make_array(make_document(kvp("city", "Ярославль")), make_document(kvp("city", "Казань")))))
Два «или» сразу make_document(kvp("$and", make_array( make_document(kvp("$or", ...)), make_document(kvp("$or", ...)))))
Отрицание условия make_document(kvp("salary", make_document(kvp("$not", make_document(kvp("$gte", 100000))))))
Ни одно из условий make_document(kvp("$nor", make_array(make_document(kvp("city", "Москва")), make_document(kvp("ready_to_move", true)))))
Хотя бы одно условие bson.D{{Key: "$or", Value: bson.A{bson.D{{Key: "city", Value: "Ярославль"}}, bson.D{{Key: "ready_to_move", Value: true}}}}}
«И» вместе с «или» bson.D{{Key: "salary", Value: bson.D{{Key: "$lte", Value: 90000}}}, {Key: "$or", Value: bson.A{bson.D{{Key: "city", Value: "Ярославль"}}, bson.D{{Key: "city", Value: "Казань"}}}}}
Два «или» сразу bson.D{{Key: "$and", Value: bson.A{ bson.D{{Key: "$or", Value: ...}}, bson.D{{Key: "$or", Value: ...}}}}}
Отрицание условия bson.D{{Key: "salary", Value: bson.D{{Key: "$not", Value: bson.D{{Key: "$gte", Value: 100000}}}}}}
Ни одно из условий bson.D{{Key: "$nor", Value: bson.A{bson.D{{Key: "city", Value: "Москва"}}, bson.D{{Key: "ready_to_move", Value: true}}}}}
Хотя бы одно условие { "$or" => [{ "city" => "Ярославль" }, { "ready_to_move" => true }] }
«И» вместе с «или» { "salary" => { "$lte" => 90000 }, "$or" => [{ "city" => "Ярославль" }, { "city" => "Казань" }] }
Два «или» сразу { "$and" => [{ "$or" => [...] }, { "$or" => [...] }] }
Отрицание условия { "salary" => { "$not" => { "$gte" => 100000 } } }
Ни одно из условий { "$nor" => [{ "city" => "Москва" }, { "ready_to_move" => true }] }

Итог главы

$or отбирает документы, для которых выполнено хотя бы одно условие из массива, и каждый документ входит в результат один раз. С остальными парами фильтра он соединяется по «и». $and пишут явно, когда два условия нельзя поставить в один документ, — обычно это два $or. $not отрицает условие с оператором и значение без оператора не принимает, $nor отбирает документы, для которых не выполнено ни одно условие. Все отрицания возвращают и документы без проверяемого поля. Следующая глава — о том, как проверить само наличие поля и тип его значения: $exists и $type.

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