Агрегация по датам
Эта агрегация по нескольким корзинам похожа на обычную гистограмму, но может использоваться только со значениями даты или диапазона дат. Поскольку даты в Elasticsearch представлены внутренне в виде длинных значений, можно, но не так точно, использовать обычный histogram и для дат. Основное различие в двух API заключается в том, что здесь интервал можно указать с помощью выражений даты/времени. Данные, основанные на времени, требуют специальной поддержки, потому что временные интервалы не всегда имеют постоянную длину.
Как и в гистограмме, значения округляются вниз до ближайшей корзины. Например, если интервал — это календарный день, 2020-01-03T07:00:01Z округляется до 2020-01-03T00:00:00Z. Значения округляются следующим образом:
bucket_key = Math.floor(value / interval) * interval
Календарные и фиксированные интервалы
При конфигурировании агрегации по датам интервал можно указать двумя способами: календарно-ориентированные временные интервалы и фиксированные временные интервалы.
Календарно-ориентированные интервалы учитывают, что переходы на летнее/зимнее время изменяют длину определённых дней, месяцы имеют разное количество дней, а високосные секунды могут быть добавлены к определённому году.
В отличие от этого, фиксированные интервалы всегда являются кратными единицам СИ и не изменяются в зависимости от календарного контекста.
Объединённое поле interval устарело
[7.2] Устарело в 7.2. interval поле устарело Исторически как календарные, так и фиксированные интервалы настраивались в одном поле interval, что приводило к путанице в семантике. Указание 1d предполагалось как календарно-ориентированное время, в то время как 2d интерпретировалось как фиксированное время. Чтобы получить «один день» фиксированного времени, пользователю необходимо было указать меньшую единицу (в данном случае, 24h).
Это поведение часто было неизвестно пользователям, и даже когда оно было известно, его было сложно использовать и понять.
Это поведение устарело в пользу двух новых явных полей: calendar_interval и fixed_interval.
Принудительно выбирая календарные и интервалы, семантика интервала становится ясной для пользователя сразу, и нет двусмысленности. Старое поле interval будет удалено в будущем.
Календарные интервалы
Календарно-ориентированные интервалы настраиваются с помощью параметра calendar_interval. Вы можете указать календарные интервалы, используя название единицы, например, month, или количество единиц, например, 1M. Например, day и 1d эквивалентны. Не поддерживаются несколько значений, таких как 2d.
Принимаемые календарные интервалы:
-
minute,1m - Все минуты начинаются с 00 секунд. Одна минута — это интервал между 00 секундами первой минуты и 00 секундами следующей минуты в указанной временной зоне, с учётом любых промежуточных високосных секунд, так что количество минут и секунд после часа одинаково в начале и в конце.
-
hour,1h - Все часы начинаются с 00 минут и 00 секунд. Один час (1ч) — это интервал между 00:00 минутами первого часа и 00:00 минутами следующего часа в указанной временной зоне, с учётом любых промежуточных високосных секунд, так что количество минут и секунд после часа одинаково в начале и в конце.
-
day,1d - Все дни начинаются с самого раннего возможного времени, что обычно составляет 00:00:00 (полночь). Один день (1д) — это интервал между началом дня и началом следующего дня в указанной временной зоне, с учётом любых промежуточных изменений времени.
-
week,1w - Одна неделя — это интервал между началом дня недели, часа, минуты и секунды и тем же днём недели и временем следующей недели в указанной временной зоне.
-
month,1M - Один месяц — это интервал между началом месяца и временем суток и тем же днём месяца и временем суток следующего месяца в указанной временной зоне, так что день месяца и время суток одинаковы в начале и в конце.
-
quarter,1q - Один квартал — это интервал между началом месяца и временем суток и тем же днём месяца и временем суток через три месяца, так что день месяца и время суток одинаковы в начале и в конце.
-
year,1y - Один год — это интервал между началом месяца и временем суток и тем же днём месяца и временем суток следующего года в указанной временной зоне, так что дата и время одинаковы в начале и в конце.
Примеры календарных интервалов
В качестве примера, вот агрегация, запрашивающая интервалы по месяцам в календарном времени:
POST /sales/_search?size=0
{
"aggs": {
"sales_over_time": {
"date_histogram": {
"field": "date",
"calendar_interval": "month"
}
}
}
} Если вы попытаетесь использовать кратные календарные единицы, агрегация завершится ошибкой, так как поддерживаются только единичные календарные единицы:
POST /sales/_search?size=0
{
"aggs": {
"sales_over_time": {
"date_histogram": {
"field": "date",
"calendar_interval": "2d"
}
}
}
} {
"error" : {
"root_cause" : [...],
"type" : "x_content_parse_exception",
"reason" : "[1:82] [date_histogram] failed to parse field [calendar_interval]",
"caused_by" : {
"type" : "illegal_argument_exception",
"reason" : "The supplied interval [2d] could not be parsed as a calendar interval.",
"stack_trace" : "java.lang.IllegalArgumentException: The supplied interval [2d] could not be parsed as a calendar interval."
}
}
} Фиксированные интервалы
Фиксированные интервалы настраиваются с помощью параметра fixed_interval.
В отличие от календарно-ориентированных интервалов, фиксированные интервалы — это фиксированное количество единиц СИ и никогда не изменяются, независимо от того, где они находятся в календаре. Одна секунда всегда состоит из 1000ms. Это позволяет указывать фиксированные интервалы в любом кратном поддерживаемых единиц.
Однако это означает, что фиксированные интервалы не могут выражать другие единицы, такие как месяцы, поскольку продолжительность месяца не является фиксированной величиной. Попытка указать календарный интервал, такой как месяц или квартал, приведёт к исключению.
Принимаемые единицы для фиксированных интервалов:
- миллисекунды (
ms) - Одна миллисекунда. Это очень короткий интервал.
- секунды (
s) - Определяется как 1000 миллисекунд каждая.
- минуты (
m) - Определяется как 60 секунд каждая (60 000 миллисекунд). Все минуты начинаются с 00 секунд.
- часы (
h) - Определяется как 60 минут каждая (3 600 000 миллисекунд). Все часы начинаются с 00 минут и 00 секунд.
- дни (
d) - Определяется как 24 часа (86 400 000 миллисекунд). Все дни начинаются с самого раннего возможного времени, что обычно составляет 00:00:00 (полночь).
Примеры фиксированных интервалов
Если мы попробуем воссоздать «месяц» calendar_interval из предыдущего примера, мы можем приблизительно заменить его на 30 фиксированных дней:
POST /sales/_search?size=0
{
"aggs": {
"sales_over_time": {
"date_histogram": {
"field": "date",
"fixed_interval": "30d"
}
}
}
} Но если мы попробуем использовать календарную единицу, которая не поддерживается, например, недели, мы получим исключение:
POST /sales/_search?size=0
{
"aggs": {
"sales_over_time": {
"date_histogram": {
"field": "date",
"fixed_interval": "2w"
}
}
}
} {
"error" : {
"root_cause" : [...],
"type" : "x_content_parse_exception",
"reason" : "[1:82] [date_histogram] failed to parse field [fixed_interval]",
"caused_by" : {
"type" : "illegal_argument_exception",
"reason" : "failed to parse setting [date_histogram.fixedInterval] with value [2w] as a time value: unit is missing or unrecognized",
"stack_trace" : "java.lang.IllegalArgumentException: failed to parse setting [date_histogram.fixedInterval] with value [2w] as a time value: unit is missing or unrecognized"
}
}
} Примечания по использованию агрегации по датам
Во всех случаях, когда указанное конечное время не существует, фактическое конечное время — это ближайшее доступное время после указанного конечного.
Широко распространённые приложения также должны учитывать особенности, такие как страны, которые начинают и заканчивают летнее/зимнее время в 12:01 ночи, что приводит к одной минуте воскресенья, за которой следует ещё 59 минут субботы раз в год, и страны, которые решают переместиться через международную дату. Ситуации такого рода могут заставить нерегулярные смещения часовых поясов казаться простыми.
Как всегда, тщательное тестирование, особенно вблизи событий перехода на другое время, гарантирует, что ваш указанный временной интервал соответствует вашим намерениям.
Чтобы избежать неожиданных результатов, все подключённые серверы и клиенты должны синхронизироваться с надёжной сетью службы времени.
Дробные значения времени не поддерживаются, но вы можете решить эту проблему, перейдя к другой единице времени (например, 1.5h можно вместо этого указать как 90m).
Вы также можете указать значения времени, используя сокращения, поддерживаемые парсингом временных единиц.
Ключи
Внутренне дата представляется 64-битным числом, представляющим отметку времени в миллисекундах с начала эпохи (01.01.1970 полночь по UTC). Эти отметки времени возвращаются в качестве имени корзины key. key_as_string — это та же отметка времени, преобразованная в отформатированную строку даты с использованием спецификации параметра format:
Если вы не укажете format, будет использоваться первый формат даты формата, указанный в отображении поля.
POST /sales/_search?size=0
{
"aggs": {
"sales_over_time": {
"date_histogram": {
"field": "date",
"calendar_interval": "1M",
"format": "yyyy-MM-dd"
}
}
}
} | Поддерживает выразительные форматы даты формата шаблона даты |
Ответ:
{
...
"aggregations": {
"sales_over_time": {
"buckets": [
{
"key_as_string": "2015-01-01",
"key": 1420070400000,
"doc_count": 3
},
{
"key_as_string": "2015-02-01",
"key": 1422748800000,
"doc_count": 2
},
{
"key_as_string": "2015-03-01",
"key": 1425168000000,
"doc_count": 2
}
]
}
}
} Временная зона
Elasticsearch хранит даты и время в формате Coordinated Universal Time (UTC). По умолчанию, все операции группирования и округления также выполняются в UTC. Используйте параметр time_zone, чтобы указать, что группировка должна использовать другую временную зону.
Например, если интервал — это календарный день, а временная зона — America/New_York, то 2020-01-03T01:00:01Z будет: # Преобразован в 2020-01-02T18:00:01 # Округлён вниз до 2020-01-02T00:00:00 # Затем преобразован обратно в UTC для получения 2020-01-02T05:00:00:00Z # Наконец, когда корзина преобразуется в строковый ключ, она отображается в America/New_York, поэтому отобразится как "2020-01-02T00:00:00".
Это выглядит так:
bucket_key = localToUtc(Math.floor(utcToLocal(value) / interval) * interval))
Вы можете указать временные зоны как смещение UTC в формате ISO 8601 (например, +01:00 или -08:00), или как идентификатор временной зоны IANA, такой как America/Los_Angeles.
Рассмотрим следующий пример:
PUT my-index-000001/_doc/1?refresh
{
"date": "2015-10-01T00:30:00Z"
}
PUT my-index-000001/_doc/2?refresh
{
"date": "2015-10-01T01:30:00Z"
}
GET my-index-000001/_search?size=0
{
"aggs": {
"by_day": {
"date_histogram": {
"field": "date",
"calendar_interval": "day"
}
}
}
} Если вы не укажете временную зону, будет использоваться UTC. Это приведет к тому, что оба документа будут помещены в одну и ту же корзину дня, которая начинается в полночь UTC 1 октября 2015 года:
{
...
"aggregations": {
"by_day": {
"buckets": [
{
"key_as_string": "2015-10-01T00:00:00.000Z",
"key": 1443657600000,
"doc_count": 2
}
]
}
}
} Если вы укажете временную зону time_zone как -01:00, полночь в этой временной зоне будет на один час раньше полуночи UTC:
GET my-index-000001/_search?size=0
{
"aggs": {
"by_day": {
"date_histogram": {
"field": "date",
"calendar_interval": "day",
"time_zone": "-01:00"
}
}
}
} Теперь первый документ попадает в корзину на 30 сентября 2015 года, а второй документ — в корзину на 1 октября 2015 года:
{
...
"aggregations": {
"by_day": {
"buckets": [
{
"key_as_string": "2015-09-30T00:00:00.000-01:00",
"key": 1443574800000,
"doc_count": 1
},
{
"key_as_string": "2015-10-01T00:00:00.000-01:00",
"key": 1443661200000,
"doc_count": 1
}
]
}
}
} | Значение |
Многие временные зоны переводят часы на летнее время. Корзины, близкие к моменту этих изменений, могут иметь немного другой размер, чем ожидается от calendar_interval или fixed_interval. Например, рассмотрим начало летнего времени в временной зоне CET: 27 марта 2016 года в 2 часа ночи часы перевели вперёд на 1 час до 3 часов местного времени. Если вы используете day в качестве calendar_interval, корзина, охватывающая этот день, будет содержать данные только в течение 23 часов вместо обычных 24 часов для других корзин. То же самое относится к более коротким интервалам, например, fixed_interval интервалу 12h, где вы получите только корзину на 11 часов утром 27 марта, когда происходит смена летнего времени.
Смещение
Используйте параметр offset, чтобы изменить начальное значение каждой корзины на указанное положительное (+) или отрицательное смещение (-) продолжительности, например, 1h для часа или 1d для дня. См. Единицы измерения времени для других возможных вариантов продолжительности времени.
Например, при использовании интервала day каждая корзина работает с полуночи до полуночи. Установка параметра offset на значение +6h изменяет каждую корзину так, чтобы она работала с 6 утра до 6 утра:
PUT my-index-000001/_doc/1?refresh
{
"date": "2015-10-01T05:30:00Z"
}
PUT my-index-000001/_doc/2?refresh
{
"date": "2015-10-01T06:30:00Z"
}
GET my-index-000001/_search?size=0
{
"aggs": {
"by_day": {
"date_histogram": {
"field": "date",
"calendar_interval": "day",
"offset": "+6h"
}
}
}
} Вместо одной корзины, начинающейся в полночь, вышеуказанный запрос группирует документы в корзины, начинающиеся в 6 утра:
{
...
"aggregations": {
"by_day": {
"buckets": [
{
"key_as_string": "2015-09-30T06:00:00.000Z",
"key": 1443592800000,
"doc_count": 1
},
{
"key_as_string": "2015-10-01T06:00:00.000Z",
"key": 1443679200000,
"doc_count": 1
}
]
}
}
} Начальное offset каждой корзины рассчитывается после внесения корректировок time_zone.
Ключевой ответ
Установка флага keyed в значение true связывает с каждой корзиной уникальный строковый ключ и возвращает диапазоны как хеш, а не как массив:
POST /sales/_search?size=0
{
"aggs": {
"sales_over_time": {
"date_histogram": {
"field": "date",
"calendar_interval": "1M",
"format": "yyyy-MM-dd",
"keyed": true
}
}
}
} Ответ:
{
...
"aggregations": {
"sales_over_time": {
"buckets": {
"2015-01-01": {
"key_as_string": "2015-01-01",
"key": 1420070400000,
"doc_count": 3
},
"2015-02-01": {
"key_as_string": "2015-02-01",
"key": 1422748800000,
"doc_count": 2
},
"2015-03-01": {
"key_as_string": "2015-03-01",
"key": 1425168000000,
"doc_count": 2
}
}
}
}
} Скрипты
Если данные в ваших документах не точно соответствуют тому, что вы хотите агрегировать, используйте поле runtime. Например, если выручка от продвинутых продаж должна учитываться на следующий день после даты продажи:
POST /sales/_search?size=0
{
"runtime_mappings": {
"date.promoted_is_tomorrow": {
"type": "date",
"script": """
long date = doc['date'].value.toInstant().toEpochMilli();
if (doc['promoted'].value) {
date += 86400;
}
emit(date);
"""
}
},
"aggs": {
"sales_over_time": {
"date_histogram": {
"field": "date.promoted_is_tomorrow",
"calendar_interval": "1M"
}
}
}
} Параметры
Вы можете контролировать порядок возвращаемых корзин с помощью настроек order и фильтровать возвращаемые корзины на основе настройки min_doc_count (по умолчанию возвращаются все корзины между первой корзиной, соответствующей документам, и последней). Эта гистограмма также поддерживает настройку extended_bounds, которая позволяет расширить границы гистограммы за пределы самих данных, и настройку hard_bounds, которая ограничивает гистограмму заданными границами. Дополнительную информацию см. в Extended Bounds и Hard Bounds.
Пропущенное значение
Параметр missing определяет, как обрабатывать документы, в которых отсутствует значение. По умолчанию они игнорируются, но также можно рассматривать их так, как будто у них есть значение.
POST /sales/_search?size=0
{
"aggs": {
"sale_date": {
"date_histogram": {
"field": "date",
"calendar_interval": "year",
"missing": "2000/01/01"
}
}
}
} | Документы без значения в поле |
Порядок
По умолчанию возвращаемые корзины отсортированы по их key в порядке возрастания, но вы можете контролировать порядок с помощью настройки order. Эта настройка поддерживает ту же функциональность order, что и Terms Aggregation.
Использование скрипта для агрегирования по дню недели
Когда вам нужно агрегировать результаты по дням недели, выполните агрегацию terms по полю runtime, которое возвращает день недели:
POST /sales/_search?size=0
{
"runtime_mappings": {
"date.day_of_week": {
"type": "keyword",
"script": "emit(doc['date'].value.dayOfWeekEnum.getDisplayName(TextStyle.FULL, Locale.ENGLISH))"
}
},
"aggs": {
"day_of_week": {
"terms": { "field": "date.day_of_week" }
}
}
} Ответ:
{
...
"aggregations": {
"day_of_week": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 0,
"buckets": [
{
"key": "Sunday",
"doc_count": 4
},
{
"key": "Thursday",
"doc_count": 3
}
]
}
}
} Ответ будет содержать все корзины, имеющие соответствующий день недели в качестве ключа: 1 для понедельника, 2 для вторника… 7 для воскресенья.
© 2023-2025 Elasticsearch
As of September 2024, Elasticsearch is available under a choice of three licenses: the Server Side Public License (SSPL), the Elastic License, or the AGPLv3 (OSI approved).
Elasticsearch and the Elasticsearch logo are trademarks of Elasticsearch B.V., registered in the U.S. and in other countries.
https://www.elastic.co/guide/en/elasticsearch/reference/7.17/search-aggregations-bucket-datehistogram-aggregation.html