Агрегирование composite
Агрегирование composite является ресурсоёмким. Проведите тестирование нагрузки вашего приложения перед применением агрегирования composite в рабочей среде.
Многоуровневое агрегирование, которое создаёт составные корзины из различных источников.
В отличие от других multi-bucket агрегирований, вы можете использовать агрегирование composite для эффективной постраничной навигации по всем корзинам из многоуровневого агрегирования. Это агрегирование позволяет потоково получать все корзины определённого агрегирования, подобно тому, как прокрутка делает это для документов.
Составные корзины формируются на основе комбинаций значений, извлечённых/созданных для каждого документа, и каждая комбинация рассматривается как составная корзина.
Например, рассмотрим следующий документ:
{
"keyword": ["foo", "bar"],
"number": [23, 65, 76]
} Использование keyword и number в качестве полей-источников для результатов агрегирования приводит к следующим составным корзинам:
{ "keyword": "foo", "number": 23 }
{ "keyword": "foo", "number": 65 }
{ "keyword": "foo", "number": 76 }
{ "keyword": "bar", "number": 23 }
{ "keyword": "bar", "number": 65 }
{ "keyword": "bar", "number": 76 } Источники значений
Параметр sources определяет поля-источники, используемые при построении составных бакетов. Порядок определения sources определяет порядок возвращаемых ключей.
При определении sources необходимо использовать уникальное имя.
Параметр sources может быть любого из следующих типов:
Термы
Источник значений terms похож на агрегацию terms. Значения извлекаются из поля аналогично агрегации terms.
Пример:
resp = client.search(
size=0,
aggs={
"my_buckets": {
"composite": {
"sources": [
{
"product": {
"terms": {
"field": "product"
}
}
}
]
}
}
},
)
print(resp) response = client.search(
body: {
size: 0,
aggregations: {
my_buckets: {
composite: {
sources: [
{
product: {
terms: {
field: 'product'
}
}
}
]
}
}
}
}
)
puts response const response = await client.search({
size: 0,
aggs: {
my_buckets: {
composite: {
sources: [
{
product: {
terms: {
field: "product",
},
},
},
],
},
},
},
});
console.log(response); GET /_search
{
"size": 0,
"aggs": {
"my_buckets": {
"composite": {
"sources": [
{ "product": { "terms": { "field": "product" } } }
]
}
}
}
} Как и в агрегации terms, для создания значений составных бакетов можно использовать поле runtime:
resp = client.search(
runtime_mappings={
"day_of_week": {
"type": "keyword",
"script": "\n emit(doc['timestamp'].value.dayOfWeekEnum\n .getDisplayName(TextStyle.FULL, Locale.ENGLISH))\n "
}
},
size=0,
aggs={
"my_buckets": {
"composite": {
"sources": [
{
"dow": {
"terms": {
"field": "day_of_week"
}
}
}
]
}
}
},
)
print(resp) const response = await client.search({
runtime_mappings: {
day_of_week: {
type: "keyword",
script:
"\n emit(doc['timestamp'].value.dayOfWeekEnum\n .getDisplayName(TextStyle.FULL, Locale.ENGLISH))\n ",
},
},
size: 0,
aggs: {
my_buckets: {
composite: {
sources: [
{
dow: {
terms: {
field: "day_of_week",
},
},
},
],
},
},
},
});
console.log(response); GET /_search
{
"runtime_mappings": {
"day_of_week": {
"type": "keyword",
"script": """
emit(doc['timestamp'].value.dayOfWeekEnum
.getDisplayName(TextStyle.FULL, Locale.ENGLISH))
"""
}
},
"size": 0,
"aggs": {
"my_buckets": {
"composite": {
"sources": [
{
"dow": {
"terms": { "field": "day_of_week" }
}
}
]
}
}
}
} Несмотря на сходство, источник значений terms не поддерживает тот же набор параметров, что и агрегация terms. Для других поддерживаемых параметров источника значений см.:
Гистограмма
Источник значений histogram может применяться к числовым значениям для создания интервалов фиксированного размера над значениями. Параметр interval определяет, как должны быть преобразованы числовые значения. Например, значение interval, равное 5, преобразует любые числовые значения к ближайшему интервалу, а значение 101 будет преобразовано в 100, что является ключом интервала между 100 и 105.
Пример:
resp = client.search(
size=0,
aggs={
"my_buckets": {
"composite": {
"sources": [
{
"histo": {
"histogram": {
"field": "price",
"interval": 5
}
}
}
]
}
}
},
)
print(resp) response = client.search(
body: {
size: 0,
aggregations: {
my_buckets: {
composite: {
sources: [
{
histo: {
histogram: {
field: 'price',
interval: 5
}
}
}
]
}
}
}
}
)
puts response const response = await client.search({
size: 0,
aggs: {
my_buckets: {
composite: {
sources: [
{
histo: {
histogram: {
field: "price",
interval: 5,
},
},
},
],
},
},
},
});
console.log(response); GET /_search
{
"size": 0,
"aggs": {
"my_buckets": {
"composite": {
"sources": [
{ "histo": { "histogram": { "field": "price", "interval": 5 } } }
]
}
}
}
} Как и в агрегации histogram, можно использовать поле runtime для создания значений составных бакетов:
resp = client.search(
runtime_mappings={
"price.discounted": {
"type": "double",
"script": "\n double price = doc['price'].value;\n if (doc['product'].value == 'mad max') {\n price *= 0.8;\n }\n emit(price);\n "
}
},
size=0,
aggs={
"my_buckets": {
"composite": {
"sources": [
{
"price": {
"histogram": {
"interval": 5,
"field": "price.discounted"
}
}
}
]
}
}
},
)
print(resp) response = client.search(
body: {
runtime_mappings: {
'price.discounted' => {
type: 'double',
script: "\n double price = doc['price'].value;\n if (doc['product'].value == 'mad max') {\n price *= 0.8;\n }\n emit(price);\n "
}
},
size: 0,
aggregations: {
my_buckets: {
composite: {
sources: [
{
price: {
histogram: {
interval: 5,
field: 'price.discounted'
}
}
}
]
}
}
}
}
)
puts response const response = await client.search({
runtime_mappings: {
"price.discounted": {
type: "double",
script:
"\n double price = doc['price'].value;\n if (doc['product'].value == 'mad max') {\n price *= 0.8;\n }\n emit(price);\n ",
},
},
size: 0,
aggs: {
my_buckets: {
composite: {
sources: [
{
price: {
histogram: {
interval: 5,
field: "price.discounted",
},
},
},
],
},
},
},
});
console.log(response); GET /_search
{
"runtime_mappings": {
"price.discounted": {
"type": "double",
"script": """
double price = doc['price'].value;
if (doc['product'].value == 'mad max') {
price *= 0.8;
}
emit(price);
"""
}
},
"size": 0,
"aggs": {
"my_buckets": {
"composite": {
"sources": [
{
"price": {
"histogram": {
"interval": 5,
"field": "price.discounted"
}
}
}
]
}
}
}
} Гистограмма по датам
date_histogram аналогичен источнику значений histogram, за исключением того, что интервал задается с помощью выражения даты/времени:
resp = client.search(
size=0,
aggs={
"my_buckets": {
"composite": {
"sources": [
{
"date": {
"date_histogram": {
"field": "timestamp",
"calendar_interval": "1d"
}
}
}
]
}
}
},
)
print(resp) response = client.search(
body: {
size: 0,
aggregations: {
my_buckets: {
composite: {
sources: [
{
date: {
date_histogram: {
field: 'timestamp',
calendar_interval: '1d'
}
}
}
]
}
}
}
}
)
puts response const response = await client.search({
size: 0,
aggs: {
my_buckets: {
composite: {
sources: [
{
date: {
date_histogram: {
field: "timestamp",
calendar_interval: "1d",
},
},
},
],
},
},
},
});
console.log(response); GET /_search
{
"size": 0,
"aggs": {
"my_buckets": {
"composite": {
"sources": [
{ "date": { "date_histogram": { "field": "timestamp", "calendar_interval": "1d" } } }
]
}
}
}
} В примере выше создается интервал на день, и все значения timestamp переводятся к началу ближайшего интервала. Доступные выражения для интервала: year, quarter, month, week, day, hour, minute, second
Значения времени также можно указать с помощью сокращений, поддерживаемых единицами измерения времени. Обратите внимание, что дробные значения времени не поддерживаются, но вы можете решить эту проблему, перейдя к другой единице измерения времени (например, 1.5h можно указать как 90m).
Формат
Внутренне дата представлена как 64-битное число, представляющее отметку времени в миллисекундах с эпохи. Эти отметки времени возвращаются в качестве ключей бакета. Можно вернуть строку отформатированной даты вместо этого, используя указанный в параметре format формат:
resp = client.search(
size=0,
aggs={
"my_buckets": {
"composite": {
"sources": [
{
"date": {
"date_histogram": {
"field": "timestamp",
"calendar_interval": "1d",
"format": "yyyy-MM-dd"
}
}
}
]
}
}
},
)
print(resp) response = client.search(
body: {
size: 0,
aggregations: {
my_buckets: {
composite: {
sources: [
{
date: {
date_histogram: {
field: 'timestamp',
calendar_interval: '1d',
format: 'yyyy-MM-dd'
}
}
}
]
}
}
}
}
)
puts response const response = await client.search({
size: 0,
aggs: {
my_buckets: {
composite: {
sources: [
{
date: {
date_histogram: {
field: "timestamp",
calendar_interval: "1d",
format: "yyyy-MM-dd",
},
},
},
],
},
},
},
});
console.log(response); GET /_search
{
"size": 0,
"aggs": {
"my_buckets": {
"composite": {
"sources": [
{
"date": {
"date_histogram": {
"field": "timestamp",
"calendar_interval": "1d",
"format": "yyyy-MM-dd"
}
}
}
]
}
}
}
} | Поддерживает выразительные форматы дат формат даты/паттерн |
Часовой пояс
Даты и время хранятся в Elasticsearch в UTC. По умолчанию все группировка и округление также выполняются в UTC. Параметр time_zone может быть использован для указания того, что группировка должна использовать другой часовой пояс.
Часовые пояса могут быть заданы либо как смещение UTC в формате ISO 8601 (например, +01:00 или -08:00), либо как идентификатор часового пояса, используемый в базе данных TZ, например America/Los_Angeles.
Смещение
Используйте параметр offset, чтобы изменить начальное значение каждого бакета на указанную положительную (+) или отрицательную величину (-) продолжительности, например, 1h для часа или 1d для дня. См. единицы измерения времени для других возможных вариантов продолжительности времени.
Например, при использовании интервала day каждый бакет начинается с полуночи. Установка параметра offset на +6h изменяет каждый бакет на период с 6:00 до 6:00:
resp = client.index(
index="my-index-000001",
id="1",
refresh=True,
document={
"date": "2015-10-01T05:30:00Z"
},
)
print(resp)
resp1 = client.index(
index="my-index-000001",
id="2",
refresh=True,
document={
"date": "2015-10-01T06:30:00Z"
},
)
print(resp1)
resp2 = client.search(
index="my-index-000001",
size="0",
aggs={
"my_buckets": {
"composite": {
"sources": [
{
"date": {
"date_histogram": {
"field": "date",
"calendar_interval": "day",
"offset": "+6h",
"format": "iso8601"
}
}
}
]
}
}
},
)
print(resp2) response = client.index(
index: 'my-index-000001',
id: 1,
refresh: true,
body: {
date: '2015-10-01T05:30:00Z'
}
)
puts response
response = client.index(
index: 'my-index-000001',
id: 2,
refresh: true,
body: {
date: '2015-10-01T06:30:00Z'
}
)
puts response
response = client.search(
index: 'my-index-000001',
size: 0,
body: {
aggregations: {
my_buckets: {
composite: {
sources: [
{
date: {
date_histogram: {
field: 'date',
calendar_interval: 'day',
offset: '+6h',
format: 'iso8601'
}
}
}
]
}
}
}
}
)
puts response const response = await client.index({
index: "my-index-000001",
id: 1,
refresh: "true",
document: {
date: "2015-10-01T05:30:00Z",
},
});
console.log(response);
const response1 = await client.index({
index: "my-index-000001",
id: 2,
refresh: "true",
document: {
date: "2015-10-01T06:30:00Z",
},
});
console.log(response1);
const response2 = await client.search({
index: "my-index-000001",
size: 0,
aggs: {
my_buckets: {
composite: {
sources: [
{
date: {
date_histogram: {
field: "date",
calendar_interval: "day",
offset: "+6h",
format: "iso8601",
},
},
},
],
},
},
},
});
console.log(response2); 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": {
"my_buckets": {
"composite" : {
"sources" : [
{
"date": {
"date_histogram" : {
"field": "date",
"calendar_interval": "day",
"offset": "+6h",
"format": "iso8601"
}
}
}
]
}
}
}
} Вместо одного бакета, начинающегося с полуночи, вышеупомянутый запрос группирует документы в бакеты, начинающиеся с 6:00:
{
...
"aggregations": {
"my_buckets": {
"after_key": { "date": "2015-10-01T06:00:00.000Z" },
"buckets": [
{
"key": { "date": "2015-09-30T06:00:00.000Z" },
"doc_count": 1
},
{
"key": { "date": "2015-10-01T06:00:00.000Z" },
"doc_count": 1
}
]
}
}
} Начальное значение offset каждого бакета вычисляется после внесения корректировок time_zone.
Сетка геоплиток
Источник значений geotile_grid работает с полями geo_point и группирует точки в бакеты, представляющие ячейки на сетке. Результирующая сетка может быть разреженной и содержать только ячейки, имеющие соответствующие данные. Каждая ячейка соответствует плитке карты, как используется во многих онлайн-картах. Каждая ячейка обозначается в формате "{zoom}/{x}/{y}", где zoom равен заданной пользователем точности.
resp = client.search(
size=0,
aggs={
"my_buckets": {
"composite": {
"sources": [
{
"tile": {
"geotile_grid": {
"field": "location",
"precision": 8
}
}
}
]
}
}
},
)
print(resp) response = client.search(
body: {
size: 0,
aggregations: {
my_buckets: {
composite: {
sources: [
{
tile: {
geotile_grid: {
field: 'location',
precision: 8
}
}
}
]
}
}
}
}
)
puts response const response = await client.search({
size: 0,
aggs: {
my_buckets: {
composite: {
sources: [
{
tile: {
geotile_grid: {
field: "location",
precision: 8,
},
},
},
],
},
},
},
});
console.log(response); GET /_search
{
"size": 0,
"aggs": {
"my_buckets": {
"composite": {
"sources": [
{ "tile": { "geotile_grid": { "field": "location", "precision": 8 } } }
]
}
}
}
} Точность
Геоплитка с максимальной точностью длиной 29 создает ячейки, покрывающие менее 10 см на 10 см суши. Эта точность идеально подходит для составных агрегаций, так как каждую плитку не нужно генерировать и загружать в память.
См. документацию уровней масштабирования, чтобы узнать, как точность (zoom) коррелирует с размером на местности. Точность для этой агрегации может быть от 0 до 29 включительно.
Фильтрация границ ограничивающей рамки
Источник geotile может быть необязательно ограничен определенной географической ограничивающей рамкой, что уменьшает диапазон используемых плиток. Эти границы полезны, когда требуется высокая точность детализации только для определенной части географической области.
resp = client.search(
size=0,
aggs={
"my_buckets": {
"composite": {
"sources": [
{
"tile": {
"geotile_grid": {
"field": "location",
"precision": 22,
"bounds": {
"top_left": "POINT (4.9 52.4)",
"bottom_right": "POINT (5.0 52.3)"
}
}
}
}
]
}
}
},
)
print(resp) response = client.search(
body: {
size: 0,
aggregations: {
my_buckets: {
composite: {
sources: [
{
tile: {
geotile_grid: {
field: 'location',
precision: 22,
bounds: {
top_left: 'POINT (4.9 52.4)',
bottom_right: 'POINT (5.0 52.3)'
}
}
}
}
]
}
}
}
}
)
puts response const response = await client.search({
size: 0,
aggs: {
my_buckets: {
composite: {
sources: [
{
tile: {
geotile_grid: {
field: "location",
precision: 22,
bounds: {
top_left: "POINT (4.9 52.4)",
bottom_right: "POINT (5.0 52.3)",
},
},
},
},
],
},
},
},
});
console.log(response); GET /_search
{
"size": 0,
"aggs": {
"my_buckets": {
"composite": {
"sources": [
{
"tile": {
"geotile_grid": {
"field": "location",
"precision": 22,
"bounds": {
"top_left": "POINT (4.9 52.4)",
"bottom_right": "POINT (5.0 52.3)"
}
}
}
}
]
}
}
}
} Смешивание различных источников значений
Параметр sources принимает массив источников значений. Можно смешивать различные источники значений для создания составных бакетов. Например:
resp = client.search(
size=0,
aggs={
"my_buckets": {
"composite": {
"sources": [
{
"date": {
"date_histogram": {
"field": "timestamp",
"calendar_interval": "1d"
}
}
},
{
"product": {
"terms": {
"field": "product"
}
}
}
]
}
}
},
)
print(resp) response = client.search(
body: {
size: 0,
aggregations: {
my_buckets: {
composite: {
sources: [
{
date: {
date_histogram: {
field: 'timestamp',
calendar_interval: '1d'
}
}
},
{
product: {
terms: {
field: 'product'
}
}
}
]
}
}
}
}
)
puts response const response = await client.search({
size: 0,
aggs: {
my_buckets: {
composite: {
sources: [
{
date: {
date_histogram: {
field: "timestamp",
calendar_interval: "1d",
},
},
},
{
product: {
terms: {
field: "product",
},
},
},
],
},
},
},
});
console.log(response); GET /_search
{
"size": 0,
"aggs": {
"my_buckets": {
"composite": {
"sources": [
{ "date": { "date_histogram": { "field": "timestamp", "calendar_interval": "1d" } } },
{ "product": { "terms": { "field": "product" } } }
]
}
}
}
} Это создаст составные бакеты из значений, созданных двумя источниками значений, date_histogram и terms. Каждый бакет состоит из двух значений, по одному для каждого источника значений, определённого в агрегации. Разрешены любые комбинации, и порядок в массиве сохраняется в составных бакетах.
resp = client.search(
size=0,
aggs={
"my_buckets": {
"composite": {
"sources": [
{
"shop": {
"terms": {
"field": "shop"
}
}
},
{
"product": {
"terms": {
"field": "product"
}
}
},
{
"date": {
"date_histogram": {
"field": "timestamp",
"calendar_interval": "1d"
}
}
}
]
}
}
},
)
print(resp) response = client.search(
body: {
size: 0,
aggregations: {
my_buckets: {
composite: {
sources: [
{
shop: {
terms: {
field: 'shop'
}
}
},
{
product: {
terms: {
field: 'product'
}
}
},
{
date: {
date_histogram: {
field: 'timestamp',
calendar_interval: '1d'
}
}
}
]
}
}
}
}
)
puts response const response = await client.search({
size: 0,
aggs: {
my_buckets: {
composite: {
sources: [
{
shop: {
terms: {
field: "shop",
},
},
},
{
product: {
terms: {
field: "product",
},
},
},
{
date: {
date_histogram: {
field: "timestamp",
calendar_interval: "1d",
},
},
},
],
},
},
},
});
console.log(response); GET /_search
{
"size": 0,
"aggs": {
"my_buckets": {
"composite": {
"sources": [
{ "shop": { "terms": { "field": "shop" } } },
{ "product": { "terms": { "field": "product" } } },
{ "date": { "date_histogram": { "field": "timestamp", "calendar_interval": "1d" } } }
]
}
}
}
} Порядок
По умолчанию составные бакеты сортируются по их естественному порядку. Значения сортируются в порядке возрастания их значений. При запросе нескольких источников значений сортировка выполняется по каждому источнику значений: первое значение составного бакета сравнивается с первым значением другого составного бакета, а если они равны, то используются следующие значения в составном бакете для определения приоритета. Это означает, что составной бакет [foo, 100] считается меньше, чем [foobar, 0], потому что foo считается меньше, чем foobar. Можно определить направление сортировки для каждого источника значений, установив order на значение asc (значение по умолчанию) или desc (по убыванию) непосредственно в определении источника значений. Например:
resp = client.search(
size=0,
aggs={
"my_buckets": {
"composite": {
"sources": [
{
"date": {
"date_histogram": {
"field": "timestamp",
"calendar_interval": "1d",
"order": "desc"
}
}
},
{
"product": {
"terms": {
"field": "product",
"order": "asc"
}
}
}
]
}
}
},
)
print(resp) response = client.search(
body: {
size: 0,
aggregations: {
my_buckets: {
composite: {
sources: [
{
date: {
date_histogram: {
field: 'timestamp',
calendar_interval: '1d',
order: 'desc'
}
}
},
{
product: {
terms: {
field: 'product',
order: 'asc'
}
}
}
]
}
}
}
}
)
puts response const response = await client.search({
size: 0,
aggs: {
my_buckets: {
composite: {
sources: [
{
date: {
date_histogram: {
field: "timestamp",
calendar_interval: "1d",
order: "desc",
},
},
},
{
product: {
terms: {
field: "product",
order: "asc",
},
},
},
],
},
},
},
});
console.log(response); GET /_search
{
"size": 0,
"aggs": {
"my_buckets": {
"composite": {
"sources": [
{ "date": { "date_histogram": { "field": "timestamp", "calendar_interval": "1d", "order": "desc" } } },
{ "product": { "terms": { "field": "product", "order": "asc" } } }
]
}
}
}
} ... отсортирует составной бакет по убыванию при сравнении значений из источника date_histogram и по возрастанию при сравнении значений из источника terms.
Отсутствующий бакет
По умолчанию документы без значения для данного источника игнорируются. Их можно включить в ответ, установив missing_bucket на true (по умолчанию false):
resp = client.search(
size=0,
aggs={
"my_buckets": {
"composite": {
"sources": [
{
"product_name": {
"terms": {
"field": "product",
"missing_bucket": True,
"missing_order": "last"
}
}
}
]
}
}
},
)
print(resp) response = client.search(
body: {
size: 0,
aggregations: {
my_buckets: {
composite: {
sources: [
{
product_name: {
terms: {
field: 'product',
missing_bucket: true,
missing_order: 'last'
}
}
}
]
}
}
}
}
)
puts response const response = await client.search({
size: 0,
aggs: {
my_buckets: {
composite: {
sources: [
{
product_name: {
terms: {
field: "product",
missing_bucket: true,
missing_order: "last",
},
},
},
],
},
},
},
});
console.log(response); GET /_search
{
"size": 0,
"aggs": {
"my_buckets": {
"composite": {
"sources": [{
"product_name": {
"terms": {
"field": "product",
"missing_bucket": true,
"missing_order": "last"
}
}
}]
}
}
}
} В приведенном выше примере источник product_name излучает явный бакет null для документов без значения product. Этот бакет размещается последним.
Вы можете управлять позицией бакета null, используя необязательный параметр missing_order. Если missing_order равно first или last, бакет null размещается соответственно на первом или последнем месте. Если missing_order опущен или равен default, позиция бакета определяется значением order источника. Если order равно asc (возрастание), бакет размещается на первом месте. Если order равно desc (убывание), бакет размещается на последнем месте.
Размер
Параметр size можно установить для определения количества возвращаемых составных бакетов. Каждый составной бакет рассматривается как один бакет, поэтому установка размера в 10 вернёт первые 10 составных бакетов, созданных из источников значений. Ответ содержит значения для каждого составного бакета в массиве, содержащем значения, извлечённые из каждого источника значений. Значение по умолчанию — 10.
Пагинация
Если количество составных бакетов слишком велико (или неизвестно), чтобы быть возвращённым в одном ответе, можно разделить запрос на несколько запросов. Поскольку составные бакеты по своей природе плоские, запрашиваемое значение size представляет собой точное количество составных бакетов, которые будут возвращены в ответе (при условии, что для возврата имеется как минимум size составных бакетов). Если необходимо получить все составные бакеты, предпочтительнее использовать небольшой размер (например, 100 или 1000), а затем использовать параметр after для получения следующих результатов. Например:
resp = client.search(
size=0,
aggs={
"my_buckets": {
"composite": {
"size": 2,
"sources": [
{
"date": {
"date_histogram": {
"field": "timestamp",
"calendar_interval": "1d"
}
}
},
{
"product": {
"terms": {
"field": "product"
}
}
}
]
}
}
},
)
print(resp) response = client.search(
body: {
size: 0,
aggregations: {
my_buckets: {
composite: {
size: 2,
sources: [
{
date: {
date_histogram: {
field: 'timestamp',
calendar_interval: '1d'
}
}
},
{
product: {
terms: {
field: 'product'
}
}
}
]
}
}
}
}
)
puts response const response = await client.search({
size: 0,
aggs: {
my_buckets: {
composite: {
size: 2,
sources: [
{
date: {
date_histogram: {
field: "timestamp",
calendar_interval: "1d",
},
},
},
{
product: {
terms: {
field: "product",
},
},
},
],
},
},
},
});
console.log(response); GET /_search
{
"size": 0,
"aggs": {
"my_buckets": {
"composite": {
"size": 2,
"sources": [
{ "date": { "date_histogram": { "field": "timestamp", "calendar_interval": "1d" } } },
{ "product": { "terms": { "field": "product" } } }
]
}
}
}
} ... вернёт:
{
...
"aggregations": {
"my_buckets": {
"after_key": {
"date": 1494288000000,
"product": "mad max"
},
"buckets": [
{
"key": {
"date": 1494201600000,
"product": "rocky"
},
"doc_count": 1
},
{
"key": {
"date": 1494288000000,
"product": "mad max"
},
"doc_count": 2
}
]
}
}
} Чтобы получить следующий набор бакетов, необходимо отправить тот же запрос агрегации с параметром after, установленным на значение after_key, возвращённое в ответе. Например, этот запрос использует значение after_key, полученное в предыдущем ответе:
resp = client.search(
size=0,
aggs={
"my_buckets": {
"composite": {
"size": 2,
"sources": [
{
"date": {
"date_histogram": {
"field": "timestamp",
"calendar_interval": "1d",
"order": "desc"
}
}
},
{
"product": {
"terms": {
"field": "product",
"order": "asc"
}
}
}
],
"after": {
"date": 1494288000000,
"product": "mad max"
}
}
}
},
)
print(resp) response = client.search(
body: {
size: 0,
aggregations: {
my_buckets: {
composite: {
size: 2,
sources: [
{
date: {
date_histogram: {
field: 'timestamp',
calendar_interval: '1d',
order: 'desc'
}
}
},
{
product: {
terms: {
field: 'product',
order: 'asc'
}
}
}
],
after: {
date: 1_494_288_000_000,
product: 'mad max'
}
}
}
}
}
)
puts response const response = await client.search({
size: 0,
aggs: {
my_buckets: {
composite: {
size: 2,
sources: [
{
date: {
date_histogram: {
field: "timestamp",
calendar_interval: "1d",
order: "desc",
},
},
},
{
product: {
terms: {
field: "product",
order: "asc",
},
},
},
],
after: {
date: 1494288000000,
product: "mad max",
},
},
},
},
});
console.log(response); GET /_search
{
"size": 0,
"aggs": {
"my_buckets": {
"composite": {
"size": 2,
"sources": [
{ "date": { "date_histogram": { "field": "timestamp", "calendar_interval": "1d", "order": "desc" } } },
{ "product": { "terms": { "field": "product", "order": "asc" } } }
],
"after": { "date": 1494288000000, "product": "mad max" }
}
}
}
} | Должен ограничивать агрегацию бакетами, которые сортируются после предоставленных значений. |
Значение after_key обычно является ключом последнего бакета, возвращённого в ответе, но это не гарантируется. Всегда используйте возвращённое значение after_key вместо его вычисления из бакетов.
Преждевременная остановка
Для оптимальной производительности сортировка индекса должна быть установлена таким образом, чтобы соответствовать частичному или полному порядку источника в агрегации. Например, следующая сортировка индекса:
resp = client.indices.create(
index="my-index-000001",
settings={
"index": {
"sort.field": [
"username",
"timestamp"
],
"sort.order": [
"asc",
"desc"
]
}
},
mappings={
"properties": {
"username": {
"type": "keyword",
"doc_values": True
},
"timestamp": {
"type": "date"
}
}
},
)
print(resp) response = client.indices.create(
index: 'my-index-000001',
body: {
settings: {
index: {
'sort.field' => [
'username',
'timestamp'
],
'sort.order' => [
'asc',
'desc'
]
}
},
mappings: {
properties: {
username: {
type: 'keyword',
doc_values: true
},
timestamp: {
type: 'date'
}
}
}
}
)
puts response const response = await client.indices.create({
index: "my-index-000001",
settings: {
index: {
"sort.field": ["username", "timestamp"],
"sort.order": ["asc", "desc"],
},
},
mappings: {
properties: {
username: {
type: "keyword",
doc_values: true,
},
timestamp: {
type: "date",
},
},
},
});
console.log(response); PUT my-index-000001
{
"settings": {
"index": {
"sort.field": [ "username", "timestamp" ],
"sort.order": [ "asc", "desc" ]
}
},
"mappings": {
"properties": {
"username": {
"type": "keyword",
"doc_values": true
},
"timestamp": {
"type": "date"
}
}
}
} | Этот индекс отсортирован по | |
| … в порядке возрастания для поля
|
resp = client.search(
size=0,
aggs={
"my_buckets": {
"composite": {
"sources": [
{
"user_name": {
"terms": {
"field": "user_name"
}
}
}
]
}
}
},
)
print(resp) response = client.search(
body: {
size: 0,
aggregations: {
my_buckets: {
composite: {
sources: [
{
user_name: {
terms: {
field: 'user_name'
}
}
}
]
}
}
}
}
)
puts response const response = await client.search({
size: 0,
aggs: {
my_buckets: {
composite: {
sources: [
{
user_name: {
terms: {
field: "user_name",
},
},
},
],
},
},
},
});
console.log(response); GET /_search
{
"size": 0,
"aggs": {
"my_buckets": {
"composite": {
"sources": [
{ "user_name": { "terms": { "field": "user_name" } } }
]
}
}
}
} |
|
resp = client.search(
size=0,
aggs={
"my_buckets": {
"composite": {
"sources": [
{
"user_name": {
"terms": {
"field": "user_name"
}
}
},
{
"date": {
"date_histogram": {
"field": "timestamp",
"calendar_interval": "1d",
"order": "desc"
}
}
}
]
}
}
},
)
print(resp) response = client.search(
body: {
size: 0,
aggregations: {
my_buckets: {
composite: {
sources: [
{
user_name: {
terms: {
field: 'user_name'
}
}
},
{
date: {
date_histogram: {
field: 'timestamp',
calendar_interval: '1d',
order: 'desc'
}
}
}
]
}
}
}
}
)
puts response const response = await client.search({
size: 0,
aggs: {
my_buckets: {
composite: {
sources: [
{
user_name: {
terms: {
field: "user_name",
},
},
},
{
date: {
date_histogram: {
field: "timestamp",
calendar_interval: "1d",
order: "desc",
},
},
},
],
},
},
},
});
console.log(response); GET /_search
{
"size": 0,
"aggs": {
"my_buckets": {
"composite": {
"sources": [
{ "user_name": { "terms": { "field": "user_name" } } },
{ "date": { "date_histogram": { "field": "timestamp", "calendar_interval": "1d", "order": "desc" } } }
]
}
}
}
} |
| |
|
|
Для оптимизации преждевременной остановки рекомендуется установить track_total_hits в запросе к false. Количество совпадающих с запросом полных попаданий можно получить при первом запросе, и его вычисление на каждой странице будет дорогостоящим:
resp = client.search(
size=0,
track_total_hits=False,
aggs={
"my_buckets": {
"composite": {
"sources": [
{
"user_name": {
"terms": {
"field": "user_name"
}
}
},
{
"date": {
"date_histogram": {
"field": "timestamp",
"calendar_interval": "1d",
"order": "desc"
}
}
}
]
}
}
},
)
print(resp) response = client.search(
body: {
size: 0,
track_total_hits: false,
aggregations: {
my_buckets: {
composite: {
sources: [
{
user_name: {
terms: {
field: 'user_name'
}
}
},
{
date: {
date_histogram: {
field: 'timestamp',
calendar_interval: '1d',
order: 'desc'
}
}
}
]
}
}
}
}
)
puts response const response = await client.search({
size: 0,
track_total_hits: false,
aggs: {
my_buckets: {
composite: {
sources: [
{
user_name: {
terms: {
field: "user_name",
},
},
},
{
date: {
date_histogram: {
field: "timestamp",
calendar_interval: "1d",
order: "desc",
},
},
},
],
},
},
},
});
console.log(response); GET /_search
{
"size": 0,
"track_total_hits": false,
"aggs": {
"my_buckets": {
"composite": {
"sources": [
{ "user_name": { "terms": { "field": "user_name" } } },
{ "date": { "date_histogram": { "field": "timestamp", "calendar_interval": "1d", "order": "desc" } } }
]
}
}
}
} Обратите внимание, что порядок источника важен. В примере ниже, замена user_name на timestamp деактивирует оптимизацию сортировки, поскольку эта конфигурация не соответствует спецификации сортировки индекса. Если порядок источников не важен для вашего случая использования, вы можете следовать этим простым правилам:
- Сначала поместите поля с наибольшей кратностью.
- Убедитесь, что порядок поля соответствует порядку сортировки индекса.
- Поместите многозначные поля в конец, так как они не могут быть использованы для преждевременной остановки.
сортировка индекса может замедлить индексацию. Очень важно протестировать сортировку индекса с вашим конкретным случаем использования и набором данных, чтобы убедиться, что она соответствует вашим требованиям. Если это не так, то агрегации composite также будут пытаться преждевременно остановиться на неотсортированных индексах, если запрос соответствует всем документам (match_all запрос).
Под-агрегации
Как и любая multi-bucket агрегация, composite агрегация может содержать под-агрегации. Эти под-агрегации могут быть использованы для вычисления других бакетов или статистических данных для каждого составного бакета, созданного этой родительской агрегацией. Например, следующий пример вычисляет среднее значение поля для каждого составного бакета:
resp = client.search(
size=0,
aggs={
"my_buckets": {
"composite": {
"sources": [
{
"date": {
"date_histogram": {
"field": "timestamp",
"calendar_interval": "1d",
"order": "desc"
}
}
},
{
"product": {
"terms": {
"field": "product"
}
}
}
]
},
"aggregations": {
"the_avg": {
"avg": {
"field": "price"
}
}
}
}
},
)
print(resp) response = client.search(
body: {
size: 0,
aggregations: {
my_buckets: {
composite: {
sources: [
{
date: {
date_histogram: {
field: 'timestamp',
calendar_interval: '1d',
order: 'desc'
}
}
},
{
product: {
terms: {
field: 'product'
}
}
}
]
},
aggregations: {
the_avg: {
avg: {
field: 'price'
}
}
}
}
}
}
)
puts response const response = await client.search({
size: 0,
aggs: {
my_buckets: {
composite: {
sources: [
{
date: {
date_histogram: {
field: "timestamp",
calendar_interval: "1d",
order: "desc",
},
},
},
{
product: {
terms: {
field: "product",
},
},
},
],
},
aggregations: {
the_avg: {
avg: {
field: "price",
},
},
},
},
},
});
console.log(response); GET /_search
{
"size": 0,
"aggs": {
"my_buckets": {
"composite": {
"sources": [
{ "date": { "date_histogram": { "field": "timestamp", "calendar_interval": "1d", "order": "desc" } } },
{ "product": { "terms": { "field": "product" } } }
]
},
"aggregations": {
"the_avg": {
"avg": { "field": "price" }
}
}
}
}
} ... возвращает:
{
...
"aggregations": {
"my_buckets": {
"after_key": {
"date": 1494201600000,
"product": "rocky"
},
"buckets": [
{
"key": {
"date": 1494460800000,
"product": "apocalypse now"
},
"doc_count": 1,
"the_avg": {
"value": 10.0
}
},
{
"key": {
"date": 1494374400000,
"product": "mad max"
},
"doc_count": 1,
"the_avg": {
"value": 27.0
}
},
{
"key": {
"date": 1494288000000,
"product": "mad max"
},
"doc_count": 2,
"the_avg": {
"value": 22.5
}
},
{
"key": {
"date": 1494201600000,
"product": "rocky"
},
"doc_count": 1,
"the_avg": {
"value": 10.0
}
}
]
}
}
} Агрегации конвейера
Агрегация `composite` в настоящее время несовместима с агрегациями конвейера, и это в большинстве случаев не имеет смысла. Например, из-за природы постраничной раздачи `composite` агрегаций, один логический раздел (один день, например) может быть распределен по нескольким страницам. Поскольку агрегации конвейера — это чисто пост-обработка конечного списка бакетов, выполнение чего-то вроде производной на странице `composite` может привести к неточным результатам, так как учитывается только «частичный» результат на этой странице.
Возможно, в будущем будут поддерживаться агрегации конвейера, которые являются самодостаточными для одного бакета (такие как bucket_selector).
© 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/8.17/search-aggregations-bucket-composite-aggregation.html