Elasticsearch среднее по дате гистограммы
У меня есть пакет документов, проиндексированных в ElasticSearch, и мне нужно получить следующие данные:
Для каждого месяца получите среднее количество документов за рабочий день месяца (или, если это невозможно, используйте 20 дней по умолчанию).
Я уже агрегировал свои данные в месячные группы, используя date histogram
агрегация. Я пытался вложить stats
сегмент, но в этих агрегатах используются данные, извлеченные из поля документа, а не из родительского сегмента.
Вот мой запрос до сих пор:
{
"query": {
"match_all": {}
},
"aggs": {
"docs_per_month": {
"date_histogram": {
"field": "created_date",
"interval": "month",
"min_doc_count": 0
}
"aggs": {
'???': '???'
}
}
}
}
редактировать
Чтобы прояснить мой вопрос, мне нужно:
- Получить общее количество номеров документов, созданных за месяц (что уже сделано благодаря
date_histogram
агрегация) - Получить количество рабочих дней за месяц
- Разделите первое на второе.
4 ответа
В основном вам нужно что-то вроде этого (что не работает, так как это недоступная функция):
{
"query": {
"match_all": {}
},
"aggs": {
"docs_per_month": {
"date_histogram": {
"field": "date",
"interval": "month",
"min_doc_count": 0
},
"aggs": {
"average": {
"avg": {
"script": "doc_count / 20"
}
}
}
}
}
}
Это не работает, потому что нет способа получить доступ к doc_count
из "родительской" агрегации.
Но это будет возможно в ветке 2.x Elasticsearch, и в данный момент она активно развивается: https://github.com/elastic/elasticsearch/issues/8110 Эта новая функция добавит второй уровень манипуляции. за результаты (сегменты) агрегации, и это не только ваш сценарий использования, но и многие другие.
Если вы не хотите попробовать какие-либо идеи или выполнить свои собственные вычисления в своем приложении, вам нужно подождать эту функцию.
Для тех, кто еще заинтересован, теперь вы можете сделать с avg_bucket
агрегация. Это все еще немного сложно, потому что вы не можете просто запустить avg_bucket
на date_historgram
результат агрегации, но с вторичным value_count
агрегация с некоторым уникальным значением, и она отлично работает:)
{
"size": 0,
"aggs": {
"orders_per_day": {
"date_histogram": {
"field": "orderedDate",
"interval": "day"
},
"aggs": {
"amount": {
"value_count": {
"field": "dateCreated"
}
}
}
},
"avg_daily_order": {
"avg_bucket": {
"buckets_path": "orders_per_day>amount"
}
}
}
}
Существует довольно запутанное и не очень эффективное решение, использующее следующее scripted_metric
агрегация
{
"size": 0,
"query": {
"match_all": {}
},
"aggs": {
"docs_per_month": {
"date_histogram": {
"field": "created_date",
"interval": "month",
"min_doc_count": 0
},
"aggs": {
"avg_doc_per_biz_day": {
"scripted_metric": {
"init_script": "_agg.bizdays = []; _agg.allbizdays = [:]; start = new DateTime(1970, 1, 1, 0, 0); now = new DateTime(); while (start < now) { def end = start.plusMonths(1); _agg.allbizdays[start.year + '_' + start.monthOfYear] = (start.toDate()..<end.toDate()).sum {(it.day != 6 && it.day != 0) ? 1 : 0 }; start = end; }",
"map_script": "_agg.bizdays << _agg.allbizdays[doc. created_date.date.year+'_'+doc. created_date.date.monthOfYear]",
"combine_script": "_agg.allbizdays = null; doc_count = 0; for (d in _agg.bizdays){ doc_count++ }; return doc_count / _agg.bizdays[0]",
"reduce_script": "res = 0; for (a in _aggs) { res += a }; return res"
}
}
}
}
}
}
Давайте подробно рассмотрим каждый сценарий ниже.
Что я делаю в init_script
создает карту количества рабочих дней для каждого месяца с 1970 года и сохраняет ее в _agg.allbizdays
карта.
_agg.bizdays = [];
_agg.allbizdays = [:];
start = new DateTime(1970, 1, 1, 0, 0);
now = new DateTime();
while (start < now) {
def end = start.plusMonths(1);
_agg.allbizdays[start.year + '_' + start.monthOfYear] = (start.toDate()..<end.toDate()).sum {(it.day != 6 && it.day != 0) ? 1 : 0 };
start = end;
}
В map_script
Я просто извлекаю количество дней недели для месяца каждого документа;
_agg.bizdays << _agg.allbizdays[doc.created_date.date.year + '_' + doc. created_date.date.monthOfYear];
В combine_script
Я суммирую среднее количество документов для каждого шарда
_agg.allbizdays = null;
doc_count = 0;
for (d in _agg.bizdays){ doc_count++ };
return doc_count / _agg.bizdays[0];
И, наконец, в reduce_script
Я суммирую среднее количество документов для каждого узла:
res = 0;
for (a in _aggs) { res += a };
return res
Опять же, я думаю, что это довольно запутанно, и, как правильно сказал Андрей, вероятно, лучше подождать, пока 2.0 заработает так, как должно, но тем временем у вас есть это решение, если оно вам нужно.
Вы хотите исключить документы с отметкой времени в субботу и воскресенье, поэтому вы можете исключить эти документы из запроса с помощью сценария
{
"query": {
"filtered": {
"filter": {
"script": {
"script": "doc['@timestamp'].date.dayOfWeek != 7 && doc['@timestamp'].date.dayOfWeek != 6"
}
}
}
},
"aggs": {
"docs_per_month": {
"date_histogram": {
"field": "created_date",
"interval": "month",
"min_doc_count": 0
},
"aggs": {
"docs_per_day": {
"date_histogram": {
"field": "created_date",
"interval": "day",
"min_doc_count": 0
}
},
"aggs": {
"docs_count": {
"avg": {
"field": ""
}
}
}
}
}
}
}
Вам может не потребоваться первая агрегация по месяцам, поскольку у вас уже есть эта информация с использованием дневного интервала
Кстати, вам нужно убедиться, что динамические сценарии включены, добавив это в свой elasticsearch.yml
конфигурация
script.disable_dynamic: false
Или добавьте groovy скрипт в /config/scripts и используйте отфильтрованный запрос со скриптом в фильтре