Согласуем выдачу и счётчики при множественном выборе фильтров.
Примеры course-products используют учебную схему и товар из главы 5 «Mapping и типы полей». Разборы упражнений ниже используют отдельный публичный набор тренажёра.
- Что означает счётчик
- query, post_filter и global
- Счёт товара и счёт значения
Согласуем выдачу и счётчики при множественном выборе фильтров.
Курс ориентирован на Elasticsearch 9.4.3. Разборы упражнений используют публичный набор products; остальные запросы показывают отдельные учебные индексы и могут требовать подготовки или прав, недоступных в тренажёре. Ответы сокращены, score может меняться. Для расширенных функций проверяйте лицензию и вариант развёртывания.
Что означает счётчик
Счётчик бренда может означать число товаров в текущей выдаче или число, которое получится после выбора бренда. Это разные интерфейсные контракты. Для независимых фасетов обычно сохраняют все активные ограничения, кроме фильтра самого фасета. Бренды A и B внутри одного выбора часто объединяются через OR, а бренд и размер — через AND. Правило должно быть явно определено.
Самопроверкаquery, post_filter и global
query ограничивает hits и агрегации. post_filter применяется к hits после расчёта агрегаций и полезен, когда счётчики должны видеть более широкое множество. global выходит за область query: внутри него нужно повторно применить обязательные ограничения доступа. Для нескольких независимых фасетов часто нужны отдельные filter aggregation с разным набором активных ограничений.
СамопроверкаСчёт товара и счёт значения
Документ с несколькими брендами или категориями может попасть в несколько buckets: сумма doc_count не обязана равняться числу уникальных товаров. Nested-фасет сначала считает вложенные документы, reverse_nested возвращает область корневых товаров. Когда нужны уникальные товары, нельзя выдавать число предложений за число товаров. Проверяйте результат на документе с двумя подходящими offers.
Hits ограничены Sony; счётчики брендов относятся ко всем совпавшим наушникам.
{
"query": {
"match": {
"name": "headphones"
}
},
"aggs": {
"brands": {
"terms": {
"field": "brand"
}
}
},
"post_filter": {
"term": {
"brand": "Sony"
}
}
}Как читать результат
Hits ограничены Sony; счётчики брендов относятся ко всем совпавшим наушникам.
Разбор на примере каталога
В категории есть Sony:5 и Bose:3. После выбора Sony выдача содержит пять товаров. Если facet brands рассчитан над этой же выдачей, Bose исчезает. Если интерфейс позволяет добавить Bose к выбору, полезнее показать оба бренда в области категории без текущего бренд-фильтра. Но цена и доступность могут оставаться активными. Для нескольких фасетов каждая область исключает только собственное ограничение; один общий post_filter не описывает автоматически все такие правила.
СамопроверкаАгрегация товаров в наличии
filter-агрегация создаёт один bucket документов, проходящих вложенный запрос. Ограничение наследуют только её дочерние агрегации. В примере hits.total по-прежнему учитывает все товары, но out_of_stock содержит недоступные и его среднее описывает только их.
Верхнеуровневый query ограничил бы все агрегации и поисковый результат. Выбирайте область по бизнес-вопросу: локальное сравнение требует filter-bucket, общее ограничение для всего — query. filter-bucket содержит doc_count, помогающий проверить набор метрики.
Разберём другой сценарий
{
"size": 0,
"aggs": {
"out_of_stock": {
"filter": {
"term": {
"in_stock": false
}
},
"aggs": {
"average_price": {
"avg": {
"field": "price"
}
}
}
}
}
}Ответ Elasticsearch
{
"hits": {
"total": {
"value": 300,
"relation": "eq"
},
"hits": []
},
"aggregations": {
"out_of_stock": {
"doc_count": 100,
"average_price": {
"value": 724.3
}
}
}
}Как читать результат
Сравните hits.total.value и out_of_stock.doc_count. Они описывают разные наборы, потому что ограничение действует локально внутри агрегации.
Условные показатели внутри групп
Разделяйте родительский счётчик и локальный допуск. Верхнеуровневый query примера выбирает active или preorder. Bucket brands считает все подходящие документы бренда. Дочерний eligible затем требует наличие И (рейтинг не ниже 4.5 ИЛИ отсутствие индексированного рейтинга). Внутренний minimum_should_match делает альтернативы рейтинга обязательными.
Отсутствующий рейтинг выражается через exists внутри bool.must_not. Наличие вынесите за альтернативы рейтинга: товар без рейтинга и без наличия не должен пройти. При нашем mapping null также не индексируется. Нулевой рейтинг существует и не проходит порог: он не должен попасть в ветвь отсутствия.
Поместите average_price, min_price и max_price в eligible.aggs. Если у бренда есть active или preorder, но нет допущенных товаров, родительский bucket остаётся с положительным doc_count, eligible.doc_count равен нулю, а все три метрики — null. Перенос допуска в query стирает это различие и отвечает на другой вопрос.
Разберём другой сценарий
{
"size": 0,
"query": {
"terms": {
"status": [
"active",
"preorder"
]
}
},
"aggs": {
"brands": {
"terms": {
"field": "brand.keyword",
"size": 20
},
"aggs": {
"eligible": {
"filter": {
"bool": {
"filter": [
{
"term": {
"in_stock": true
}
}
],
"should": [
{
"range": {
"rating": {
"gte": 4.5
}
}
},
{
"bool": {
"must_not": [
{
"exists": {
"field": "rating"
}
}
]
}
}
],
"minimum_should_match": 1
}
},
"aggs": {
"average_price": {
"avg": {
"field": "price"
}
},
"min_price": {
"min": {
"field": "price"
}
},
"max_price": {
"max": {
"field": "price"
}
}
}
}
}
}
}
}Ответ Elasticsearch
{
"hits": {
"total": {
"value": 200,
"relation": "eq"
},
"hits": []
},
"aggregations": {
"brands": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 0,
"buckets": [
{
"key": "Apple",
"doc_count": 50,
"eligible": {
"doc_count": 19,
"min_price": {
"value": 49
},
"average_price": {
"value": 864.8421052631579
},
"max_price": {
"value": 1299
}
}
},
{
"key": "Logitech",
"doc_count": 50,
"eligible": {
"doc_count": 15,
"min_price": {
"value": 99
},
"average_price": {
"value": 919.7333333333333
},
"max_price": {
"value": 2499
}
}
},
{
"key": "Sony",
"doc_count": 50,
"eligible": {
"doc_count": 0,
"min_price": {
"value": null
},
"average_price": {
"value": null
},
"max_price": {
"value": null
}
}
},
{
"key": "apple",
"doc_count": 50,
"eligible": {
"doc_count": 0,
"min_price": {
"value": null
},
"average_price": {
"value": null
},
"max_price": {
"value": null
}
}
}
]
}
}
}Как читать результат
Сравните doc_count бренда и eligible.doc_count: они могут различаться. В фиксированных данных некоторые бренды не имеют допущенных товаров: ответ явно показывает случай null-метрик.
Самопроверка
Сначала ответьте самостоятельно, затем раскройте объяснение.
1. Почему сумма buckets больше total?
Один многозначный документ входит в несколько групп.
2. Что меняет post_filter?
Hits, но не уже рассчитанную область агрегаций.
3. Чем опасен global в multi-tenant?
Он выходит за query, поэтому ограничения tenant нужно применить внутри явно.
4. Что возвращает reverse_nested?
Область корневых документов из nested-контекста.
5. Как объединять выбранные бренды?
Обычно OR внутри фасета, при явно зафиксированном контракте интерфейса.
Краткая памятка
- Один многозначный документ входит в несколько групп.
- Hits, но не уже рассчитанную область агрегаций.
- Он выходит за query, поэтому ограничения tenant нужно применить внутри явно.
- Область корневых документов из nested-контекста.
- Обычно OR внутри фасета, при явно зафиксированном контракте интерфейса.
Официальная документация
Готовы применить знания?
Подтвердите прочтение, чтобы сохранить своё место. Чтение и решение заданий учитываются отдельно в этом браузере с помощью гостевой cookie.