Подсчитать все термины в запросе

Есть ли возможность найти счетчик для каждого термина в запросе?

Например. У меня есть следующие утверждения для подсчета:

(age == 20 || age == 30) && gender == 'male'

И я хотел бы вернуть общее количество + субсчета для всех терминов, используя одиночный вызов отдыха.

Ожидаемые результаты подсчета:

  1. age == 20
  2. age == 30
  3. age == 20 || age == 30
  4. gender == 'male'
  5. (age == 20 || age == 30) && gender == 'male'

Пример поискового запроса, созданного для этого конкретного сценария:

{
  "query": {
    "bool": {
      "must": [
        {
          "bool": {
            "should": [
              {
                "term": {
                  "age": { "value": 20,"boost": 1 } // count 1
                }
              },
              {
                "term": {
                  "age": { "value": 30,"boost": 1 } // count 2
                }
              }
            ],
            "adjust_pure_negative": true, "boost": 1
          } // count 3
        },
        {
          "term": {
            "gender.keyword": { "value": "male", "boost": 1 } // count 4
          }
        }
      ],
      "adjust_pure_negative": true,
      "boost": 1
    } // count 5
  }
}
0
0
140
1

Ответы 1

Обновлено для подсчета произвольных условных выражений

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

{
  "aggs": {
    "conditions": {
      "filters": {
        "filters": {
          "age == 20": {"term": {"age": 20}},
          "age == 30": {"term": {"age": 30}},
          "age == 20 || age == 30": {
            "bool": {
              "should": [
                {"term": {"age": 20}},
                {"term": {"age": 30}}
              ]
            }
          },
          "gender == male": {"term": {"gender.keyword": "male"}},
          "(age == 20 || age == 30) && gender == 'male'": {
            "bool": {
              "must": [
                {"term": {"gender.keyword": "male"}}
              ],
              "should": [
                {"term": {"age": 20}},
                {"term": {"age": 30}}
              ]
            }
          }
        }
      }
    }
  }
}

Даем вам такой результат:

{
  "aggregations": {
    "conditions": {
      "buckets": {
        "(age == 20 || age == 30) && gender == 'male'": {
          "doc_count": 12
        },
        "age == 20": {
          "doc_count": 8
        },
        "age == 20 || age == 30": {
          "doc_count": 19
        },
        "age == 30": {
          "doc_count": 11
        },
        "gender == male": {
          "doc_count": 12
        }
      }
    }
  }
}

Обновлено: исходный ответ, который не обрабатывал (A || B) правильно

Функция, которую вы ищете, называется агрегированием, в частности Агрегация терминов. Агрегаты терминов будут подсчитывать количество документов для каждого возможного значения поля в наборе результатов, соответствующем вашему предложению запроса. Вы также можете вкладывать агрегаты. Итак, в приведенном ниже примере Elasticearch найдет все документы, соответствующие вашему запросу, затем подсчитает, сколько документов соответствует каждому возрасту (20, 30 и т. д.), А затем для каждого возраста подсчитает, сколько документов соответствует каждому полу. Затем вы можете выполнить математические вычисления, чтобы вычислить различные запрошенные комбинации.

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

{
  "query": {
    ...
  },
  "aggs": {
    "age": {
      "terms": {"field": "age"},
      "aggs": {
        "gender": {
          "terms": {"field": "gender"}
        }
      }
    },
    "gender_total": {"terms": {"field": "gender"}}
  }
}

Результат будет примерно таким:

{
  "hits": { ... },
  "aggregations": {
    "gender_total": {
      "doc_count_error_upper_bound": 0,
      "sum_other_doc_count": 0,
      "buckets": [
        {
          "key": "male",
          "doc_count": 12
        },
        {
          "key": "female",
          "doc_count": 7
        }
      ]
    },
    "age": {
      "doc_count_error_upper_bound": 0,
      "sum_other_doc_count": 0,
      "buckets": [
        {
          "key": 30,
          "doc_count": 11,
          "gender": {
            "doc_count_error_upper_bound": 0,
            "sum_other_doc_count": 0,
            "buckets": [
              {
                "key": "male",
                "doc_count": 9
              },
              {
                "key": "female",
                "doc_count": 2
              }
            ]
          }
        },
        {
          "key": 20,
          "doc_count": 8,
          "gender": {
            "doc_count_error_upper_bound": 0,
            "sum_other_doc_count": 0,
            "buckets": [
              {
                "key": "female",
                "doc_count": 5
              },
              {
                "key": "male",
                "doc_count": 3
              }
            ]
          }
        }
      ]
    }
  }
}

Так, например, чтобы вычислить счетчик для (age == 20 || age == 30) && gender == 'male', вы можете сделать что-то вроде этого псевдокода python:

# Pull out the bucket objects for each aggregation
age_buckets = result['aggregations']['age']['buckets']
gender_buckets = result['aggregations']['gender_total']['buckets']

# Get the bucket values we care about
age_20 = [b for b in age_buckets if b['key'] == 20][0]
age_30 = [b for b in age_buckets if b['key'] == 30][0]
male = [b for b in gender_buckets if b['key'] == 'male'][0]

# Get the sub-buckets
age_20_male = [b for b in age_20['gender']['buckets'] if b['key'] == 'male'][0]
age_30_male = [b for b in age_30['gender']['buckets'] if b['key'] == 'male'][0]

# age == 20
count_1 = age_20['doc_count']

# age == 30
count_2 = age_30['doc_count']

# age == 20 || age == 30
count_3 = count_1 + count_2

# gender == 'male'
count_4 = male['doc_count']

# (age == 20 || age == 30) && gender == 'male'
count = age_20_male['doc_count'] + age_30_male['doc_count']

Эти агрегаты довольно полезны, но я думаю, что они не подходят для моего сценария. Насколько я понимаю нет возможности быстро посчитать stmt OR. Например, в случае, если вместо этого у меня есть условие age>30 OR expirience>10 - мне пришлось бы реализовать цикл n ^ 2, чтобы найти реальный счет, потому что некоторые строки могут соответствовать обоим условиям, и их не следует подсчитывать дважды. Я знаю, что это возможно, но в более сложном случае, вероятно, использование нескольких запросов для эластичного поиска для каждого возможного сценария будет быстрее, чем подсчет самостоятельно.

SZMER 13.08.2018 10:23

Я думаю, что код, который я опубликовал изначально, будет работать для условий, которые вы указали в своем исходном вопросе. Но я обновлю способ подсчета произвольных условий.

Ryan Widmaier 13.08.2018 16:58

Не будет. Например, у вас есть следующий набор [<a,b,c>, <a>, <b>], и вы ищете количество элементов, содержащих a || b: в вашем решении результатом будет 4, потому что количество a == 2 и количество b == 2, но реальное значение это 3, потому что 1 элемент содержит оба значения.

SZMER 14.08.2018 17:49

Да, верно. Полагаю, я предполагал, что у вас не будет нескольких значений «возраст» или «пол». Тем не менее, подход filter agg должен работать, если вы определили все нужные вам предложения.

Ryan Widmaier 14.08.2018 19:19

Другие вопросы по теме