Могут ли агрегаты ElasticSearch делать то же, что и SQL?

В Elasticsearch мне нужно получить частоту и количество цветов, которые встречаются чаще всего, от самого высокого до самого низкого. Если у меня есть такие данные:

id|name
----------
1|blue
----------
2|blue
----------
3|green
----------
4|yellow
----------
5|blue
----------
6|yellow
----------
7|purple
----------
8|purple
----------
9|purple

Мне нужно получить количество каждого цвета, а затем сгруппировать по количеству. Итак, в конце концов, я хотел бы, чтобы все цвета, которые встречаются одинаковое количество раз, находились внутри одной группы. Вот как бы я сделал это в sql.

select 
  count(*) as 'Number of Colors', 
  i.c as 'Seen times' 
from 
    (
      select
        name as 'n', 
        count(*) as 'c'
      from
        colors
      group by name
   ) i 
group by i.c
order by i.c desc;

Это вернет:

Number of Colors | Seen times
------------------------------
2                | 3
------------------------------
1                | 2
------------------------------
1                | 1

Как бы я написал это в запросе Elasticsearch? Я использую версию 5.5.


person widjl    schedule 24.01.2018    source источник
comment
какой запрос Elasticsearch?   -  person Vijunav Vastivch    schedule 24.01.2018
comment
elastic.co/guide/en/elasticsearch/reference/6.1/   -  person widjl    schedule 24.01.2018
comment
Таблица результатов неверна, числа в левом и правом столбцах надо поменять местами, т.е. 3 цвета видели 2 раза, 2 цвета видели 1 раз и 1 цвет видели 1 раз.   -  person Val    schedule 02.02.2018


Ответы (2)


Вы можете использовать scripted_metric агрегация безболезненным скриптом

Запрос

Я объясню это более подробно ниже.

POST index/type/_search
{
  "size": 0, 
  "aggs": {
    "colorgroups": {
"scripted_metric": {
"init_script" : "params._agg.transactions = [:]",
"map_script": "params.key = doc['colorname'].value; if(params._agg.transactions[params.key] == null){   params._agg.transactions[params.key] = 1; }else{   params._agg.transactions[params.key] ++ }",
"combine_script": "return params._agg.transactions;",
"reduce_script": "params.color_counters =[:]; params.groups_counters =[:]; for(shard_result in params._aggs){   for(color_name in shard_result.keySet()){     if(params.color_counters[color_name] == null){       params.color_counters[color_name] = shard_result[color_name]     }else{       params.color_counters[color_name] = params.color_counters[color_name] + shard_result[color_name]     }    } }  for(color_name in params.color_counters.keySet()){   params.group_counter = params.color_counters[color_name].toString();   if(params.groups_counters[params.group_counter] == null){     params.groups_counters[params.group_counter] = 1   }else{     params.groups_counters[params.group_counter] ++   } }  return params.groups_counters"
      }
    }
  }
}

Результат

{
  "took": 1,
  "timed_out": false,
  "_shards": {
    "total": 5,
    "successful": 5,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": 9,
    "max_score": 0,
    "hits": []
  },
  "aggregations": {
    "colorgroups": {
      "value": {
        "2": 3,
        "1": 2,
        "1": 1
     }
    }
  }
}

init_script

инициализируйте некоторые значения, чтобы сохранить промежуточные результаты

params._agg.transactions = [:]

map_script

Расчеты по каждому документу. Постарайтесь сделать его небольшим и сократите как можно больше данных на этом шаге.

params.key = doc['colorname'].value;
if(params._agg.transactions[params.key] == null){
  params._agg.transactions[params.key] = 1;
}else{
  params._agg.transactions[params.key] ++
}

комбинированный_скрипт

Расчеты для каждого шарда. Мы уже все сделали в map_script, просто возвращаем hash map

return params._agg.transactions

уменьшить_скрипт

Работа с частичными агрегациями из каждого шарда. Сначала нам нужно объединить их в один HashMap. И сгруппировать по значению счетчика после

params.color_counters =[:];
params.groups_counters =[:];
//merging all partial aggregations to params.color_counters
for(shard_result in params._aggs){
  for(color_name in shard_result.keySet()){
    if(params.color_counters[color_name] == null){
      params.color_counters[color_name] = shard_result[color_name]
    }else{
      params.color_counters[color_name] = params.color_counters[color_name] + shard_result[color_name]
    } 
  }
}

//Grouping by color counter to params.groups_counters
for(color_name in params.color_counters.keySet()){
  params.group_counter = params.color_counters[color_name].toString();
  if(params.groups_counters[params.group_counter] == null){
    params.groups_counters[params.group_counter] = 1
  }else{
    params.groups_counters[params.group_counter] ++
  }
}

return params.groups_counters

Вы можете позвонить

Debug.explain(variable);

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

person pkhlop    schedule 07.02.2018

По умолчанию эластичный поиск не предоставляет какой-либо тип агрегации, который использует некоторые результаты агрегации для получения выходных данных нового набора агрегаций, таких как SQL.

  1. Один из способов сделать это — агрегировать термины, а затем группировать их с помощью скрипта.
  2. Если вы хотите добиться этого в одном запросе, вы можете использовать для этого скриптовое агрегирование показателей. Для получения более подробной информации используйте ссылку ниже:

https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-metrics-scripted-metric-aggregation.html

person Raghav salotra    schedule 08.02.2018