Составные запросы: bool и комбинирование условий
match, term, range — каждый из этих запросов решает одну задачу. В предыдущей статье мы разобрали точные фильтры, и в финальном примере они уже оказались внутри bool.filter. Пришло время разобрать bool-запрос целиком — он и есть каркас, в который монтируются любые условия.
Четыре секции bool
bool сам по себе не задаёт условие — он комбинирует другие запросы через четыре секции:
{
"query": {
"bool": {
"must": [...],
"filter": [...],
"should": [...],
"must_not": [...]
}
}
}Все четыре секции опциональны: можно использовать одну, можно все сразу.
flowchart LR
Q([bool query]) --> M[must]
Q --> F[filter]
Q --> S[should]
Q --> N[must_not]
style Q fill:#e8f4f8,stroke:#4a90d9,color:#000
style M fill:#d4edda,stroke:#28a745,color:#000
style F fill:#cce5ff,stroke:#004085,color:#000
style S fill:#fff3cd,stroke:#856404,color:#000
style N fill:#f8d7da,stroke:#721c24,color:#000
M --- MA["Обязательно\nВлияет на _score"]
F --- FA["Обязательно\nНе влияет на _score\nКешируется"]
S --- SA["Опционально\nВлияет на _score"]
N --- NA["Исключает документы\nКешируется"]must: обязательно и влияет на оценку
Документ обязан совпасть с каждым условием в must. Каждый клоз вносит вклад в итоговый _score — чем точнее совпадение, тем выше оценка. Если клозов несколько, их скоры суммируются:
Пример — ищем документы, где в заголовке есть «ноутбук» и в описании есть «SSD»:
GET /products/_search
{
"query": {
"bool": {
"must": [
{ "match": { "title": "ноутбук" } },
{ "match": { "description": "SSD" } }
]
}
}
}Документ, который лучше совпадает с обоими условиями сразу, окажется выше в выдаче.
filter: обязательно, без влияния на оценку
filter тоже требует совпадения, но не участвует в расчёте _score — это бинарное «да/нет». Elasticsearch кеширует результаты filter-клозов: если тот же фильтр встречается в следующем запросе, ES использует готовый список документов без повторного обхода индекса.
Классический паттерн — полнотекстовый поиск в must, бизнес-фильтры в filter:
GET /products/_search
{
"query": {
"bool": {
"must": [
{ "match": { "title": "ноутбук" } }
],
"filter": [
{ "term": { "status": "in_stock" } },
{ "range": { "price": { "lte": 100000 } } }
]
}
}
}match ранжирует ноутбуки по релевантности. Фильтры по статусу и цене просто отсекают неподходящие — без влияния на порядок выдачи.
Правило: всё, что не должно менять ранжирование (статус, цена, диапазон дат, наличие поля) — в filter, а не в must. Подробнее о разнице контекстов — в статье Query DSL: структура запроса и контекст query vs filter.
must_not: исключение из результатов
Документ, совпавший с любым условием в must_not, полностью исключается из выдачи. На _score не влияет; работает в filter context — результаты кешируются.
GET /products/_search
{
"query": {
"bool": {
"must": [
{ "match": { "title": "ноутбук" } }
],
"must_not": [
{ "term": { "brand": "NoName" } },
{ "term": { "status": "discontinued" } }
]
}
}
}Ноутбуки марки NoName и снятые с производства не попадут в результаты — остальные ранжируются как обычно.
should: предпочтение, не требование
Это самая тонкая секция. При наличии must или filter условия в should необязательны: документ попадёт в выдачу, даже если ни одно из них не выполнено. Но каждое совпавшее should-условие добавляет к _score:
GET /products/_search
{
"query": {
"bool": {
"must": [
{ "match": { "title": "ноутбук" } }
],
"should": [
{ "term": { "brand": "apple" } },
{ "range": { "rating": { "gte": 4.5 } } }
]
}
}
}Ноутбук без Apple и с низким рейтингом всё равно появится в выдаче — но окажется ниже ноутбуков, которые совпали с одним или обоими should-клозами.
Если в bool нет must и filter — только should — поведение меняется: документ обязан совпасть хотя бы с одним клозом. Это аналог OR:
GET /products/_search
{
"query": {
"bool": {
"should": [
{ "match": { "title": "ноутбук" } },
{ "match": { "title": "laptop" } }
]
}
}
}minimum_should_match: сколько should обязаны совпасть
По умолчанию при наличии must/filter совпадение с нулём should-клозов — это нормально. Параметр minimum_should_match поднимает этот порог:
GET /products/_search
{
"query": {
"bool": {
"filter": [
{ "term": { "category": "смартфоны" } }
],
"should": [
{ "term": { "brand": "apple" } },
{ "term": { "brand": "samsung" } },
{ "term": { "brand": "google" } }
],
"minimum_should_match": 1
}
}
}Теперь смартфон обязан быть от Apple, Samsung или Google — остальные бренды отсекаются. При этом совпавший бренд всё равно добавляет к _score — это не просто фильтр, а мягкое ранжирование одновременно.
minimum_should_match принимает несколько форматов:
- целое число:
1,2 - процент от числа клозов:
"75%" - относительное выражение:
"-1"(все клозы, кроме одного)
Вложенные bool: составная логика
Плоский bool не может выразить (A AND B) OR C в одном уровне. Для этого вкладывают один bool внутрь другого:
GET /products/_search
{
"query": {
"bool": {
"should": [
{
"bool": {
"must": [
{ "match": { "title": "ноутбук" } },
{ "term": { "brand": "apple" } }
]
}
},
{ "match": { "title": "MacBook" } }
],
"minimum_should_match": 1
}
}
}Документ совпадёт, если: «ноутбук» И бренд Apple — или «MacBook» в заголовке. Каждый вложенный bool считает свой скор, который суммируется в родительском.
Итог: когда какую секцию использовать
| Секция | Совпадение обязательно? | Влияет на _score | Кешируется |
|---|---|---|---|
must | Да | Да | Нет |
filter | Да | Нет | Да |
should | Нет (если есть must/filter) | Да | Нет |
must_not | — (исключает) | Нет | Да |
См. также
- Query DSL: структура запроса и контекст query vs filter
- Полнотекстовые запросы: match, match_phrase, multi_match
- Точные совпадения и фильтры: term, range, exists
- Релевантность и BM25: как считается _score
- Бустинг полей и function_score: управление ранжированием
- Подсветка, сортировка и пагинация результатов