# Помогите составить агрегирующий запрос с фильтром

**URL:** <https://discuss.elastic.co/t/topic/59429>\
**Category:** Вопросы на русском языке\
**Created:** [August 31, 2016, 1:36pm UTC](https://discuss.elastic.co/t/topic/59429 "2016-08-31T13:36:22Z")\
**Posts on this page:** 19\
**Page:** 1

<div class="post-metadata">

**Author:** ![Alkalit](https://avatars.discourse-cdn.com/v4/letter/a/57b2e6/32.png) [@Alkalit](https://discuss.elastic.co/u/Alkalit)\
**Post date:** [August 31, 2016, 1:36pm UTC](https://discuss.elastic.co/t/topic/59429/1 "2016-08-31T13:36:22Z")

</div>

Здравствуйте. Я уже задавал этот вопрос неделю назад на английском языке, но ответа не получил, а он очень нужен. Мне надо составить запрос на агрегирование с фильтрованием.

Дано - есть 3 таблицы базы данных - Объявление, Фильтр и Категория Фильтра (Ad, Filter, FilterCategory соответственно). Версия эластика - 2.3  
Ad имеет связь m2m с Filter и с Filter прооброшен fk на FilterCategory. Т.е. у объявления и категории может быть множество фильтров. Объявления проиндексированы в эластик. Вот пример документа с продакшена [http://pastebin.com/hY6aneyz](http://pastebin.com/hY6aneyz)

где фильтры объявления обозначены полями - filters\_ids, filters\_names и filters\_slugs  
а категории - filter\_categories\_ids, filter\_categories\_names и filter\_categories\_slugs

Надо - отфильтровать и агрегировать объявления по запросу с примерно таким псевдокодом:

```auto
WHERE (filter = 'othodyi' OR filter = 'pervichka') AND (category = 'po-forme' AND category = 'po-vidu')

```

Т.е. у объявления обязательно должны присутствовать все категории и любые из фильтров

P.S. на самом деле все еще немного сложнее - на проекте с эластиком работаем не напрямую, а через "обертку" - [https://github.com/django-haystack/django-haystack](https://github.com/django-haystack/django-haystack)  
Но в вопросе это не принципиально.

---

<div class="post-metadata">

**Author:** ![Igor\_Motov](https://sea2.discourse-cdn.com/elastic/user_avatar/discuss.elastic.co/igor_motov/32/45193_2.png) [@Igor\_Motov](https://discuss.elastic.co/u/Igor_Motov)\
**Post date:** [August 31, 2016, 3:06pm UTC](https://discuss.elastic.co/t/topic/59429/2 "2016-08-31T15:06:16Z")

</div>

Если `filters_slags` и `filters_categories_slugs` проиндексированы как `not_analyzed`, то фильтр может выглядеть примерно так

```auto
{
  "query": {
    "constant_score" : {
      "filter" : {
        "bool" : {
          "should": [
             {"term": {"filters_slugs" : "othodyi"}},
             {"term": {"filters_slags" : "pervichka"}}
          ],
         "must": [
             {"term": {"filter_categories_slugs" : "po-forme"}},
             {"term": {"filter_categories_slugs" : "po-vidu"}}
         ]
        }
      }
    }
  }
}

```

---

<div class="post-metadata">

**Author:** ![Alkalit](https://avatars.discourse-cdn.com/v4/letter/a/57b2e6/32.png) [@Alkalit](https://discuss.elastic.co/u/Alkalit)\
**Post date:** [September 5, 2016, 3:01pm UTC](https://discuss.elastic.co/t/topic/59429/3 "2016-09-05T15:01:00Z")

</div>

Благодарю за быстрый ответ. Запрос работает, но дает не тот результат который я ожидал, поэтому мне придется задать вопрос по-другому.

Мне надо реализовать выборку объявлений по их классификации. Например, если у нас есть такие объявления:

```auto
Объявление 1
    по-цвету
        красный
        черный 
    по-форме
        гранула
        порошок
        крошка

Объявление 2
    по-цвету
        красный 

Объявление 3
    по-форме
        гранула
        крошка

Объявление 4
    по-цвету
        белый 
    по-типу
        пп

```

Тогда изначальная выборка по ним будет выглядеть так:

```auto
по-форме
    гранула 2
    крошка 2
    порошок 1

по-цвету
    красный 2
    черный 1
    белый 1

по-типу
    пп 1

```

Если юзер выберет "крошка", то "по-форме" не изменится, но "по-цвету" будут только "красный" и "черный" - 1, т.к. только одно объявление соответствует каждому из них, и категория "по-типу" не войдет в выборку. Соответственно мне нужен запрос который это выполнит.

---

<div class="post-metadata">

**Author:** ![Igor\_Motov](https://sea2.discourse-cdn.com/elastic/user_avatar/discuss.elastic.co/igor_motov/32/45193_2.png) [@Igor\_Motov](https://discuss.elastic.co/u/Igor_Motov)\
**Post date:** [September 5, 2016, 4:31pm UTC](https://discuss.elastic.co/t/topic/59429/4 "2016-09-05T16:31:28Z")

</div>

Я не понимаю, как из вашего документа в примере вытащить эти данные:

```auto
Объявление 1
    по-цвету
        красный
        черный 
    по-форме
        гранула
        порошок
        крошка

```

---

<div class="post-metadata">

**Author:** ![Alkalit](https://avatars.discourse-cdn.com/v4/letter/a/57b2e6/32.png) [@Alkalit](https://discuss.elastic.co/u/Alkalit)\
**Post date:** [September 5, 2016, 4:39pm UTC](https://discuss.elastic.co/t/topic/59429/5 "2016-09-05T16:39:18Z")

</div>

Это поля `filters_names` и `filter_categories_names`. Или вы имеете в виду что в таком виде документ не пригоден для реализации такого поиска?

---

<div class="post-metadata">

**Author:** ![Igor\_Motov](https://sea2.discourse-cdn.com/elastic/user_avatar/discuss.elastic.co/igor_motov/32/45193_2.png) [@Igor\_Motov](https://discuss.elastic.co/u/Igor_Motov)\
**Post date:** [September 5, 2016, 5:06pm UTC](https://discuss.elastic.co/t/topic/59429/6 "2016-09-05T17:06:49Z")

</div>

У вас документ в индексе представлен в виде

```auto
Объявление 1
    категории
        по-цвету
        по-форме
    фильтры 
        гранула
        красный
        крошка
        порошок
        черный 

```

Я не понимаю, как из этого сделать вот это

```auto
Объявление 1
    по-цвету
        красный
        черный 
    по-форме
        гранула
        порошок
        крошка

```

Другими словами, где храниться информация, что порошок это форма и красный - это цвет?

---

<div class="post-metadata">

**Author:** ![Alkalit](https://avatars.discourse-cdn.com/v4/letter/a/57b2e6/32.png) [@Alkalit](https://discuss.elastic.co/u/Alkalit)\
**Post date:** [September 6, 2016, 2:03pm UTC](https://discuss.elastic.co/t/topic/59429/7 "2016-09-06T14:03:15Z")

</div>

Понял вас. Добавил поле `classification`. Для документа из оппоста оно будет выглядеть так:

```auto
    "classification": [
      {
        "filters_slugs": [
          "othodyi",
          "vtorichka"
        ],
        "category_id": 1
      },
      {
        "filters_slugs": [
          "pnd",
          "pvd",
          "pvh",
          "abs",
          "pp",
          "pa",
          "ps",
          "polietilen",
          "lpvd"
        ],
        "category_id": 3
      },
      {
        "filters_slugs": [
          "ekstruziya",
          "lite",
          "formovanie"
        ],
        "category_id": 5
      },
      {
        "filters_slugs": [
          "krasnyij",
          "belyij",
          "chernyij",
          "sinij",
          "zelenyij",
          "zheltyij"
        ],
        "category_id": 7
      }
    ],

```

В маппинге:

```auto
    "classification": {
        "type": "nested",
        "properties": {
          "category_id": {
            "type": "long"
          },
          "filters_slugs": {
            "type": "string",
            "index": "not_analyzed"
          }
        }
      },

```

---

<div class="post-metadata">

**Author:** ![Igor\_Motov](https://sea2.discourse-cdn.com/elastic/user_avatar/discuss.elastic.co/igor_motov/32/45193_2.png) [@Igor\_Motov](https://discuss.elastic.co/u/Igor_Motov)\
**Post date:** [September 6, 2016, 2:53pm UTC](https://discuss.elastic.co/t/topic/59429/8 "2016-09-06T14:53:25Z")

</div>

В этом случае, можно сделать что-нибудь вроде:

```auto
{
  "size": 0,
  "aggs": {
    "categories": {
      "nested": {
        "path": "classification"
      },
      "aggs": {
        "category_id": {
          "terms": {
            "field": "classification.category_id"
          },
          "aggs": {
            "filters_id": {
              "terms": {
                "field": "classification.filters_slugs"
              }
            }
          }
        }
      }
    }
  }
} 

```

Но если категории не часто обновляются, и их количество не очень большое, то более оптимальное решение было бы создать отдельные поля для каждой категории. Тогда можно было бы обойтись без nested.

---

<div class="post-metadata">

**Author:** ![Alkalit](https://avatars.discourse-cdn.com/v4/letter/a/57b2e6/32.png) [@Alkalit](https://discuss.elastic.co/u/Alkalit)\
**Post date:** [September 7, 2016, 2:14pm UTC](https://discuss.elastic.co/t/topic/59429/9 "2016-09-07T14:14:46Z")

</div>

А вы проницательны. Да, так можно сделать, но пока из-за особенностей рабочего кода лучше оставить как есть.

А как в таком случае будет выглядеть запрос? Насколько я понимаю, псевдокод будте примерно таким:

`WHERE ((classification.category_id=category1 AND classification.filters_slugs=filter1) OR (classification.category_id=category1 AND classification.filters_slugs=filter2)) AND (classification.category_id=category2 AND classification.filters_slugs=filter3)`

Пытаюсь написать такой запрос: [http://pastebin.com/QaKCSuLj](http://pastebin.com/QaKCSuLj)

но он не отрабатывает из-за nested части.

---

<div class="post-metadata">

**Author:** ![Igor\_Motov](https://sea2.discourse-cdn.com/elastic/user_avatar/discuss.elastic.co/igor_motov/32/45193_2.png) [@Igor\_Motov](https://discuss.elastic.co/u/Igor_Motov)\
**Post date:** [September 7, 2016, 2:45pm UTC](https://discuss.elastic.co/t/topic/59429/10 "2016-09-07T14:45:57Z")

</div>

`nested` не на том уровне. Ваш запрос пытается найти одну классификацию, которая одновременно и в 6-й и в 7-й категории.

```auto
{
  "query": {
    "constant_score":{
      "filter":{
        "bool": {
          "must": [
            {"term": {"ad_type": "sell"} },
            {"term": {"category_slug": "syire"} },
            {
              "bool": {
                "must": [
                  {
                    "nested": {
                      "path": "classification",
                      "query": {
                        "bool":{
                          "must":[
                             {"term":{"classification.category_id":6}},
                             {"term":{"classification.filters_slugs":"granula"}}
                          ]
                        }
                      }
                    }
                  },
                  {
                   "bool":{
                     "should":[
                        {
                          "nested": {
                            "path": "classification",
                            "query": {
                             "bool":{
                                "must":[
                                   {"term":{"classification.category_id":7 }},
                                   {"term":{"classification.filters_slugs":"chernyij" }}
                                ]
                              }
                            }
                          }
                        },
                        {
                          "nested": {
                            "path": "classification",
                            "query": {
                             "bool":{
                                "must":[
                                   {"term":{"classification.category_id":7}},
                                   {"term":{"classification.filters_slugs": "belyij"}}
                                ]
                             }
                            }
                          }
                        }
                      ]
                    }
                  }
                ]
              }
            }
          ]
        }
      }
    }
  }
}

```

---

<div class="post-metadata">

**Author:** ![Alkalit](https://avatars.discourse-cdn.com/v4/letter/a/57b2e6/32.png) [@Alkalit](https://discuss.elastic.co/u/Alkalit)\
**Post date:** [September 19, 2016, 2:04pm UTC](https://discuss.elastic.co/t/topic/59429/11 "2016-09-19T14:04:35Z")

</div>

Боюсь что в Лапландском королевстве все опять не так как надо. Запрос работает и в при выборе фильтров он показывает действительные числа. Проблема с теми фильтрами которые не были выделены и соответственно не вошли в запрос - они "проседают".  
Покажу на примере:  
стартовая выборка

![](https://us1.discourse-cdn.com/elastic/original/2X/5/59f0e31c0f1bb6fb10765522c8d981d6c6b60b4e.png)  
и здесь мы выбрали несколько фильтров.  
 ![](https://us1.discourse-cdn.com/elastic/original/2X/2/29ce0bab65fdff4f7a8a4cfb7dac21de9885d350.png)  
У нас действительно 758 объявлений с тегами "белый" и "гранула" и 1819 объявлений с тегами "гранула" и "белый" или "черный". Но например объявлений с "красный" и "гранула" в действительности 62.

Мне надо реализовать этот функционал аналогично [заппос](http://www.zappos.com) , например как на [этой](http://www.zappos.com/hats~1) странице.

P.S. Извините что сразу с этого не начал.

---

<div class="post-metadata">

**Author:** ![Igor\_Motov](https://sea2.discourse-cdn.com/elastic/user_avatar/discuss.elastic.co/igor_motov/32/45193_2.png) [@Igor\_Motov](https://discuss.elastic.co/u/Igor_Motov)\
**Post date:** [September 19, 2016, 2:48pm UTC](https://discuss.elastic.co/t/topic/59429/12 "2016-09-19T14:48:38Z")

</div>

Посмотрите документацию для `terms`[тут](https://www.elastic.co/guide/en/elasticsearch/reference/1.7/search-aggregations-bucket-terms-aggregation.html#search-aggregations-bucket-terms-aggregation-approximate-counts).

---

<div class="post-metadata">

**Author:** ![Alkalit](https://avatars.discourse-cdn.com/v4/letter/a/57b2e6/32.png) [@Alkalit](https://discuss.elastic.co/u/Alkalit)\
**Post date:** [September 19, 2016, 4:33pm UTC](https://discuss.elastic.co/t/topic/59429/13 "2016-09-19T16:33:08Z")

</div>

Прочел. Потом заметил что ссылка для 1.7 и прочел ту же статью для 2.3. Я так понимаю вы намекаете на Multi-field terms aggregationedit ? Но я не понимаю какое отношение это имеет к моей проблеме.

---

<div class="post-metadata">

**Author:** ![Igor\_Motov](https://sea2.discourse-cdn.com/elastic/user_avatar/discuss.elastic.co/igor_motov/32/45193_2.png) [@Igor\_Motov](https://discuss.elastic.co/u/Igor_Motov)\
**Post date:** [September 19, 2016, 4:41pm UTC](https://discuss.elastic.co/t/topic/59429/14 "2016-09-19T16:41:20Z")

</div>

Я, наверное, опять не понял Ваш вопрос. Вы спросили почему

> [@Alkalit](#):
>
> Но например объявлений с "красный" и "гранула" в действительности 62.

Я прислал ссылку, которая объясняет почему [document counts are approximate](https://www.elastic.co/guide/en/elasticsearch/reference/2.3/search-aggregations-bucket-terms-aggregation.html#search-aggregations-bucket-terms-aggregation-approximate-counts) и как с этим бороться.

---

<div class="post-metadata">

**Author:** ![Alkalit](https://avatars.discourse-cdn.com/v4/letter/a/57b2e6/32.png) [@Alkalit](https://discuss.elastic.co/u/Alkalit)\
**Post date:** [September 21, 2016, 11:53am UTC](https://discuss.elastic.co/t/topic/59429/15 "2016-09-21T11:53:45Z")

</div>

Хорошо, тогда я попробую начать с самого начала и подробнее. У нас есть 3 таблицы: "Объявление" с m2m на "Фильтр" с fk на "Категория фильтра".  
На работе дали задание сделать фасетную навигацию по объявлениям с учетом того как они отклассифицированы (это фильтры и их категории), по образу популярных коммерческих площадок. Например:

[http://www.ozon.ru/catalog/1137927/?newperiod=4](http://www.ozon.ru/catalog/1137927/?newperiod=4)  
[http://www.tehnosila.ru/catalog/kompjutery\_i\_orgtehnika/monitory\_lcd](http://www.tehnosila.ru/catalog/kompjutery_i_orgtehnika/monitory_lcd)

> **[TVs - Best deals on PriceRunner UK](https://www.pricerunner.co.uk/cl/2/TVs)**
>
> Compare prices on TVs on PriceRunner to help you find the best deal online

И вот тут-то и начинаются проблемы. Как человек привыкший к sql, я даже не могу точно описать принцип по которому там происходит выборка.  
Т.е. в общих словах это действительно "ИЛИ" внутри фасетов и "И" между ними. Но мне не очевидно почему, например у той же техносилы, при выборе пары брендов в мониторах "проседать" начинают другие фасеты, кроме текущего. Я только знаю что это реализовано с использованием эластика или солра.

Запрос который вы мне помогли составить похож на то что мне нужно, но он правильно работает только для выбранных фильтров. Т.е. возвращаясь к предыдущему своему посту, "красный", как я понял, отображает количество объявлений из тех где есть "гранула" и ("белый" или "черный"). А должен отображать количество объявлений где есть "красный" и "гранула", даже при том что "красный" не выделен.

P.S. Про document counts are approximate я знаю и везде где это критично стоит `size: 0`.

---

<div class="post-metadata">

**Author:** ![Igor\_Motov](https://sea2.discourse-cdn.com/elastic/user_avatar/discuss.elastic.co/igor_motov/32/45193_2.png) [@Igor\_Motov](https://discuss.elastic.co/u/Igor_Motov)\
**Post date:** [September 21, 2016, 7:27pm UTC](https://discuss.elastic.co/t/topic/59429/16 "2016-09-21T19:27:32Z")

</div>

Я все равно не понимаю, что вы хотите. Может быть [это](https://www.elastic.co/guide/en/elasticsearch/guide/2.x/_post_filter.html)? Если нет, вы не могли бы привести какой-нибудь простой пример - вот 10 записей, вот поля в них, вот запрос, вот что я хочу видеть?

---

<div class="post-metadata">

**Author:** ![Yan\_Work](https://sea2.discourse-cdn.com/elastic/user_avatar/discuss.elastic.co/yan_work/32/620_2.png) [@Yan\_Work](https://discuss.elastic.co/u/Yan_Work)\
**Post date:** [October 1, 2016, 2:57am UTC](https://discuss.elastic.co/t/topic/59429/17 "2016-10-01T02:57:22Z")

</div>

Использовать составной запрос

- запрос\_для вытаскивания товаров, фильтры - А, Б, В
- глобальная агрегация, сбрасывающая скоуп, в нее попадает все
- внутри глобальной агрегации три фильтрующих под-агрегации
- одна из них имеет фильтром Б, В (все кроме А), при этом она делает term по A
- вторая их них имеет фильтром А, В, при это она делает агрегацию по Б
- третья имеет фильтром Б, В, при этом она делает агрегацию по В

итого запрос выглядит примерно так  
Q = A, B, C

- GA
- 
  - FA

- 
  - 
    - filter B C

- 
  - 
    - term A

- 
  - FB

- 
  - 
    - filter A C

- 
  - 
    - term B

- 
  - FC

- 
  - 
    - filter A B

- 
  - 
    - term C

Итого, допустим у нас 5 документов  
док1 = красная большая машина  
док2 = красный большой дом  
док3 = красный большой квадрат  
док4 = красный маленький дом  
док5 = желтый большой дом

запрос = красный + большой + дом  
глобальная агрегация  
агрегация по цвету = ищется в рамках размер + тип  
агрегация по размеру = ищется в рамках цвет + тип  
агрегация по типу = ищется в рамках цвет + размер

итого, в результате будет  
тотал = 1  
хит = док2 = красный большой дом  
агрегация по цвету = красный (1) + желтый (1) (потому что ищет большие дома)  
агрегация по размеру = большой (1) + маленький (1) (потому что ищет красные дома)  
агрегация по типу = дом (1) + квадрат (1) (потому что ищет красный + большой)

Если непонятно, задавайте уточняющий вопрос

---

<div class="post-metadata">

**Author:** ![Yan\_Work](https://sea2.discourse-cdn.com/elastic/user_avatar/discuss.elastic.co/yan_work/32/620_2.png) [@Yan\_Work](https://discuss.elastic.co/u/Yan_Work)\
**Post date:** [October 1, 2016, 2:58am UTC](https://discuss.elastic.co/t/topic/59429/18 "2016-10-01T02:58:09Z")

</div>

Это все звучит довольно громоздко, но в некоторых случаях кластер справляется с такими запросами приемлемо (тестируйте)

---

<div class="post-metadata">

**Author:** ![system](https://us1.discourse-cdn.com/elastic/original/3X/1/a/1ac57faf039f6b580b3f104ef42a2a89e41014de.png) [@system](https://discuss.elastic.co/u/system)\
**Post date:** [July 6, 2017, 1:45pm UTC](https://discuss.elastic.co/t/topic/59429/19 "2017-07-06T13:45:11Z")

</div>


