8

Я играю с ES, чтобы понять, может ли он охватывать большинство моих сценариев. Я нахожусь в точке, где я застрял, думая, как достичь определенных результатов, которые довольно просты в SQL.Elasticsearch SQL как агрегация подзапросов

Это пример

В упругом У меня есть индекс с этим документом

{ "Id": 1, "Fruit": "Banana", "BoughtInStore"="Jungle", "BoughtDate"=20160101, "BestBeforeDate": 20160102, "BiteBy":"John"} 
{ "Id": 2, "Fruit": "Banana", "BoughtInStore"="Jungle", "BoughtDate"=20160102, "BestBeforeDate": 20160104, "BiteBy":"Mat"} 
{ "Id": 3, "Fruit": "Banana", "BoughtInStore"="Jungle", "BoughtDate"=20160103, "BestBeforeDate": 20160105, "BiteBy":"Mark"} 
{ "Id": 4, "Fruit": "Banana", "BoughtInStore"="Jungle", "BoughtDate"=20160104, "BestBeforeDate": 20160201, "BiteBy":"Simon"} 
{ "Id": 5, "Fruit": "Orange", "BoughtInStore"="Jungle", "BoughtDate"=20160112, "BestBeforeDate": 20160112, "BiteBy":"John"} 
{ "Id": 6, "Fruit": "Orange", "BoughtInStore"="Jungle", "BoughtDate"=20160114, "BestBeforeDate": 20160116, "BiteBy":"Mark"} 
{ "Id": 7, "Fruit": "Orange", "BoughtInStore"="Jungle", "BoughtDate"=20160120, "BestBeforeDate": 20160121, "BiteBy":"Simon"} 
{ "Id": 8, "Fruit": "Kiwi", "BoughtInStore"="Shop", "BoughtDate"=20160121, "BestBeforeDate": 20160121, "BiteBy":"Mark"} 
{ "Id": 8, "Fruit": "Kiwi", "BoughtInStore"="Jungle", "BoughtDate"=20160121, "BestBeforeDate": 20160121, "BiteBy":"Simon"} 

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

SELECT 
    COUNT(DISTINCT kpi.Fruit) as Fruits, 
    kpi.BoughtInStore, 
    kpi.BiteBy 
FROM 
    (
     SELECT f1.Fruit, f1.BoughtInStore, f1.BiteBy 
     FROM FruitsTable f1 
     WHERE f1.BoughtDate = (
      SELECT MAX(f2.BoughtDate) 
      FROM FruitsTable f2 
      WHERE f1.Fruit = f2.Fruit 
      and f2.BoughtDate between 20160101 and 20160131 
      and (f2.BestBeforeDate between 20160101 and 20160131) 
     ) 
    ) kpi 
GROUP BY kpi.BoughtInStore, kpi.ByteBy 

результатов что-то вроде этого

{ "Fruits": 1, "BoughtInStore": "Jungle", "BiteBy"="Mark"} 
{ "Fruits": 1, "BoughtInStore": "Shop", "BiteBy"="Mark"} 
{ "Fruits": 2, "BoughtInStore": "Jungle", "BiteBy"="Simon"} 

Вы знаете, как я могу достичь того же результата в Elastic с агрегацией?

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

  1. Как подготовить subsed данных до агрегирования (как в этом примере последней строки диапазона в каждом плоде)
  2. Как для групповых результатов по нескольким полям

Спасибо

ответ

2

Как я понимаю, что нет никакого способа передать результат агрегации в фильтре одного и того же запроса. Таким образом, вы можете решить только часть головоломки с одним запросом:

GET /purchases/fruits/_search 
{ 
    "query": { 
    "filtered":{ 
     "filter": { 
     "range": { 
      "BoughtDate": { 
      "gte": "2015-01-01", //assuming you have right mapping for dates 
      "lte": "2016-03-01" 
      } 
     } 
     } 
    } 
    }, 
    "sort": { "BoughtDate": { "order": "desc" }}, 
    "aggs": { 
    "byBoughtDate": { 
     "terms": { 
     "field": "BoughtDate", 
     "order" : { "_term" : "desc" } 
     }, 
     "aggs": { 
     "distinctCount": { 
      "cardinality": { 
      "field": "Fruit" 
      } 
     } 
     } 
    } 
    } 
} 

Таким образом, вы будете иметь все документы в пределах диапазона дат, и вы будете иметь агрегированные отсчеты ведра, отсортированные по срокам, так максимальная дата будет на вершине. Клиент может проанализировать это первое ведро (количество и значение), а затем взять документы для этого значения даты. Для определенного количества фруктов вы просто используете вложенную агрегацию мощности.

Да, запрос возвращает гораздо больше информации, чем нужно, но это жизнь :)

1

Естественно нет прямого пути от SQL к Elasticsearch DSL, но есть некоторые довольно общие корреляции.

Для начала любая GROUP BY/HAVING собирается свести к агрегации. Обычная семантика запроса обычно может быть покрыта (и тем более) DSL запроса.

Как подготовить subsed данных до агрегирования (как в этом примере последней строки в диапазоне на каждом плоде)

Итак, вы вроде просят две разные вещи.

Как подготовить subsed данных до агрегирования

Это фаза запроса.

(как в этом примере последней строки в диапазоне на каждый плод)

Вы технически просим его агрегировать, чтобы получить ответ на этот пример: не обычный запрос. В вашем примере вы делаете MAX, чтобы получить это, действующее с помощью GROUP BY, чтобы получить его.

Как сгруппируйте результаты по нескольким полям

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

Если вы хотите, чтобы они были многоуровневыми, то вы просто используете субгруппы, чтобы получить то, что хотите. Если вы хотите, чтобы они были объединены, тогда вы обычно используете агрегацию filters для разных группировок.

Взяв все это вместе: вам нужна самая последняя покупка за каждый фрукт, учитывая определенный диапазон фильтров. Диапазоны дат просто нормальные запросы/фильтры:

{ 
    "query": { 
    "bool": { 
     "filter": [ 
     { 
      "range": { 
      "BoughtDate": { 
       "gte": "2016-01-01", 
       "lte": "2016-01-31" 
      } 
      } 
     }, 
     { 
      "range": { 
      "BestBeforeDate": { 
       "gte": "2016-01-01", 
       "lte": "2016-01-31" 
      } 
      } 
     } 
     ] 
    } 
    } 
} 

При том, что ни один документ не будет включен в запрос, который не находится в пределах этих диапазонов для обоих полей (фактически является AND). Поскольку я использовал фильтр, он незащищен и кэшируется.

Теперь вам нужно начать агрегирование, чтобы получить остальную информацию. Начнем с того, что документы были отфильтрованы с использованием вышеуказанного фильтра, чтобы упростить то, на что мы смотрим. Мы объединим его в конце.

{ 
    "size": 0, 
    "aggs": { 
    "group_by_date": { 
     "date_histogram": { 
     "field": "BoughtDate", 
     "interval": "day", 
     "min_doc_count": 1 
     }, 
     "aggs": { 
     "group_by_store": { 
      "terms": { 
      "field": "BoughtInStore" 
      }, 
      "aggs": { 
      "group_by_person": { 
       "terms": { 
       "field": "BiteBy" 
       } 
      } 
      } 
     } 
     } 
    } 
    } 
} 

Вы хотите "size" : 0 на самом высоком уровне, потому что вы на самом деле не волнует хитов. Вам нужны только агрегированные результаты.

Ваша первая агрегация фактически группировалась по самой последней дате. Я немного изменил его, чтобы сделать его немного более реалистичным (каждый день), но это фактически то же самое. То, как вы используете MAX, мы могли бы использовать агрегацию terms с "size": 1, но это truer, как вы хотели бы сделать это, когда речь идет о дате (и предположительно времени!). Я также попросил его игнорировать дни в соответствующих документах, у которых нет данных (так как это происходит от начала до конца, на самом деле мы не заботимся о тех днях).

Если у вас действительно нужен только последний день, то вы можете использовать агрегацию конвейера, чтобы удалить все, кроме макс. Ковша, но для реалистичного использования этого типа запроса потребуется полный диапазон дат.

Итак, мы продолжим сгруппировать по магазинам, что вам нужно. Затем мы подгруппируем человека (BiteBy). Это даст вам счет неявно.

Собираем все вместе:

{ 
    "size": 0, 
    "query": { 
    "bool": { 
     "filter": [ 
     { 
      "range": { 
      "BoughtDate": { 
       "gte": "2016-01-01", 
       "lte": "2016-01-31" 
      } 
      } 
     }, 
     { 
      "range": { 
      "BestBeforeDate": { 
       "gte": "2016-01-01", 
       "lte": "2016-01-31" 
      } 
      } 
     } 
     ] 
    } 
    }, 
    "aggs": { 
    "group_by_date": { 
     "date_histogram": { 
     "field": "BoughtDate", 
     "interval": "day", 
     "min_doc_count": 1 
     }, 
     "aggs": { 
     "group_by_store": { 
      "terms": { 
      "field": "BoughtInStore" 
      }, 
      "aggs": { 
      "group_by_person": { 
       "terms": { 
       "field": "BiteBy" 
       } 
      } 
      } 
     } 
     } 
    } 
    } 
} 

Примечание: Вот как я индексироваться данные.

PUT /grocery/store/_bulk 
{"index":{"_id":"1"}} 
{"Fruit":"Banana","BoughtInStore":"Jungle","BoughtDate":"2016-01-01","BestBeforeDate":"2016-01-02","BiteBy":"John"} 
{"index":{"_id":"2"}} 
{"Fruit":"Banana","BoughtInStore":"Jungle","BoughtDate":"2016-01-02","BestBeforeDate":"2016-01-04","BiteBy":"Mat"} 
{"index":{"_id":"3"}} 
{"Fruit":"Banana","BoughtInStore":"Jungle","BoughtDate":"2016-01-03","BestBeforeDate":"2016-01-05","BiteBy":"Mark"} 
{"index":{"_id":"4"}} 
{"Fruit":"Banana","BoughtInStore":"Jungle","BoughtDate":"2016-01-04","BestBeforeDate":"2016-02-01","BiteBy":"Simon"} 
{"index":{"_id":"5"}} 
{"Fruit":"Orange","BoughtInStore":"Jungle","BoughtDate":"2016-01-12","BestBeforeDate":"2016-01-12","BiteBy":"John"} 
{"index":{"_id":"6"}} 
{"Fruit":"Orange","BoughtInStore":"Jungle","BoughtDate":"2016-01-14","BestBeforeDate":"2016-01-16","BiteBy":"Mark"} 
{"index":{"_id":"7"}} 
{"Fruit":"Orange","BoughtInStore":"Jungle","BoughtDate":"2016-01-20","BestBeforeDate":"2016-01-21","BiteBy":"Simon"} 
{"index":{"_id":"8"}} 
{"Fruit":"Kiwi","BoughtInStore":"Shop","BoughtDate":"2016-01-21","BestBeforeDate":"2016-01-21","BiteBy":"Mark"} 
{"index":{"_id":"9"}} 
{"Fruit":"Kiwi","BoughtInStore":"Jungle","BoughtDate":"2016-01-21","BestBeforeDate":"2016-01-21","BiteBy":"Simon"} 

Это критическое что ваши строковые значения, которые вы хотите агрегировать на (магазин и человек) являются not_analyzedstring s (keyword в ES 5.0)! В противном случае он будет использовать то, что называется fielddata, и это не очень хорошо.

Отображения будет выглядеть следующим образом в ES 1.x/ES 2.x:

PUT /grocery 
{ 
    "settings": { 
    "number_of_shards": 1 
    }, 
    "mappings": { 
    "store": { 
     "properties": { 
     "Fruit": { 
      "type": "string", 
      "index": "not_analyzed" 
     }, 
     "BoughtInStore": { 
      "type": "string", 
      "index": "not_analyzed" 
     }, 
     "BiteBy": { 
      "type": "string", 
      "index": "not_analyzed" 
     }, 
     "BestBeforeDate": { 
      "type": "date" 
     }, 
     "BoughtDate": { 
      "type": "date" 
     } 
     } 
    } 
    } 
} 

Все это вместе, и вы получите ответ, как:

{ 
    "took": 8, 
    "timed_out": false, 
    "_shards": { 
    "total": 1, 
    "successful": 1, 
    "failed": 0 
    }, 
    "hits": { 
    "total": 8, 
    "max_score": 0, 
    "hits": [] 
    }, 
    "aggregations": { 
    "group_by_date": { 
     "buckets": [ 
     { 
      "key_as_string": "2016-01-01T00:00:00.000Z", 
      "key": 1451606400000, 
      "doc_count": 1, 
      "group_by_store": { 
      "doc_count_error_upper_bound": 0, 
      "sum_other_doc_count": 0, 
      "buckets": [ 
       { 
       "key": "Jungle", 
       "doc_count": 1, 
       "group_by_person": { 
        "doc_count_error_upper_bound": 0, 
        "sum_other_doc_count": 0, 
        "buckets": [ 
        { 
         "key": "John", 
         "doc_count": 1 
        } 
        ] 
       } 
       } 
      ] 
      } 
     }, 
     { 
      "key_as_string": "2016-01-02T00:00:00.000Z", 
      "key": 1451692800000, 
      "doc_count": 1, 
      "group_by_store": { 
      "doc_count_error_upper_bound": 0, 
      "sum_other_doc_count": 0, 
      "buckets": [ 
       { 
       "key": "Jungle", 
       "doc_count": 1, 
       "group_by_person": { 
        "doc_count_error_upper_bound": 0, 
        "sum_other_doc_count": 0, 
        "buckets": [ 
        { 
         "key": "Mat", 
         "doc_count": 1 
        } 
        ] 
       } 
       } 
      ] 
      } 
     }, 
     { 
      "key_as_string": "2016-01-03T00:00:00.000Z", 
      "key": 1451779200000, 
      "doc_count": 1, 
      "group_by_store": { 
      "doc_count_error_upper_bound": 0, 
      "sum_other_doc_count": 0, 
      "buckets": [ 
       { 
       "key": "Jungle", 
       "doc_count": 1, 
       "group_by_person": { 
        "doc_count_error_upper_bound": 0, 
        "sum_other_doc_count": 0, 
        "buckets": [ 
        { 
         "key": "Mark", 
         "doc_count": 1 
        } 
        ] 
       } 
       } 
      ] 
      } 
     }, 
     { 
      "key_as_string": "2016-01-12T00:00:00.000Z", 
      "key": 1452556800000, 
      "doc_count": 1, 
      "group_by_store": { 
      "doc_count_error_upper_bound": 0, 
      "sum_other_doc_count": 0, 
      "buckets": [ 
       { 
       "key": "Jungle", 
       "doc_count": 1, 
       "group_by_person": { 
        "doc_count_error_upper_bound": 0, 
        "sum_other_doc_count": 0, 
        "buckets": [ 
        { 
         "key": "John", 
         "doc_count": 1 
        } 
        ] 
       } 
       } 
      ] 
      } 
     }, 
     { 
      "key_as_string": "2016-01-14T00:00:00.000Z", 
      "key": 1452729600000, 
      "doc_count": 1, 
      "group_by_store": { 
      "doc_count_error_upper_bound": 0, 
      "sum_other_doc_count": 0, 
      "buckets": [ 
       { 
       "key": "Jungle", 
       "doc_count": 1, 
       "group_by_person": { 
        "doc_count_error_upper_bound": 0, 
        "sum_other_doc_count": 0, 
        "buckets": [ 
        { 
         "key": "Mark", 
         "doc_count": 1 
        } 
        ] 
       } 
       } 
      ] 
      } 
     }, 
     { 
      "key_as_string": "2016-01-20T00:00:00.000Z", 
      "key": 1453248000000, 
      "doc_count": 1, 
      "group_by_store": { 
      "doc_count_error_upper_bound": 0, 
      "sum_other_doc_count": 0, 
      "buckets": [ 
       { 
       "key": "Jungle", 
       "doc_count": 1, 
       "group_by_person": { 
        "doc_count_error_upper_bound": 0, 
        "sum_other_doc_count": 0, 
        "buckets": [ 
        { 
         "key": "Simon", 
         "doc_count": 1 
        } 
        ] 
       } 
       } 
      ] 
      } 
     }, 
     { 
      "key_as_string": "2016-01-21T00:00:00.000Z", 
      "key": 1453334400000, 
      "doc_count": 2, 
      "group_by_store": { 
      "doc_count_error_upper_bound": 0, 
      "sum_other_doc_count": 0, 
      "buckets": [ 
       { 
       "key": "Jungle", 
       "doc_count": 1, 
       "group_by_person": { 
        "doc_count_error_upper_bound": 0, 
        "sum_other_doc_count": 0, 
        "buckets": [ 
        { 
         "key": "Simon", 
         "doc_count": 1 
        } 
        ] 
       } 
       }, 
       { 
       "key": "Shop", 
       "doc_count": 1, 
       "group_by_person": { 
        "doc_count_error_upper_bound": 0, 
        "sum_other_doc_count": 0, 
        "buckets": [ 
        { 
         "key": "Mark", 
         "doc_count": 1 
        } 
        ] 
       } 
       } 
      ] 
      } 
     } 
     ] 
    } 
    } 
} 
+0

Как прямо сейчас , мое небольшое замеченное обходное решение с агрегацией ковша для ограничения максимальной даты не будет работать с 'date_histogram'. По иронии судьбы, это сработало бы, если бы я просто оставил значения как цифры, как вы изначально показывали. – pickypg