Агрегация диапазонов
Базовая агрегация по значениям с множеством корзин, позволяющая пользователю определить набор диапазонов — каждый из которых представляет собой корзину. В процессе агрегации значения, извлеченные из каждого документа, будут проверяться на соответствие каждому диапазону корзины, и соответствующие документы будут помещены в корзину. Обратите внимание, что эта агрегация включает значение from и исключает значение to для каждого диапазона.
Пример:
resp = client.search(
index="sales",
aggs={
"price_ranges": {
"range": {
"field": "price",
"ranges": [
{
"to": 100
},
{
"from": 100,
"to": 200
},
{
"from": 200
}
]
}
}
},
)
print(resp) response = client.search(
index: 'sales',
body: {
aggregations: {
price_ranges: {
range: {
field: 'price',
ranges: [
{
to: 100
},
{
from: 100,
to: 200
},
{
from: 200
}
]
}
}
}
}
)
puts response const response = await client.search({
index: "sales",
aggs: {
price_ranges: {
range: {
field: "price",
ranges: [
{
to: 100,
},
{
from: 100,
to: 200,
},
{
from: 200,
},
],
},
},
},
});
console.log(response); GET sales/_search
{
"aggs": {
"price_ranges": {
"range": {
"field": "price",
"ranges": [
{ "to": 100.0 },
{ "from": 100.0, "to": 200.0 },
{ "from": 200.0 }
]
}
}
}
} Ответ:
{
...
"aggregations": {
"price_ranges": {
"buckets": [
{
"key": "*-100.0",
"to": 100.0,
"doc_count": 2
},
{
"key": "100.0-200.0",
"from": 100.0,
"to": 200.0,
"doc_count": 2
},
{
"key": "200.0-*",
"from": 200.0,
"doc_count": 3
}
]
}
}
} Ответ с ключами
Установка флага keyed в значение true свяжет с каждой корзиной уникальный строковый ключ и вернёт диапазоны в виде хэша, а не массива:
resp = client.search(
index="sales",
aggs={
"price_ranges": {
"range": {
"field": "price",
"keyed": True,
"ranges": [
{
"to": 100
},
{
"from": 100,
"to": 200
},
{
"from": 200
}
]
}
}
},
)
print(resp) response = client.search(
index: 'sales',
body: {
aggregations: {
price_ranges: {
range: {
field: 'price',
keyed: true,
ranges: [
{
to: 100
},
{
from: 100,
to: 200
},
{
from: 200
}
]
}
}
}
}
)
puts response const response = await client.search({
index: "sales",
aggs: {
price_ranges: {
range: {
field: "price",
keyed: true,
ranges: [
{
to: 100,
},
{
from: 100,
to: 200,
},
{
from: 200,
},
],
},
},
},
});
console.log(response); GET sales/_search
{
"aggs": {
"price_ranges": {
"range": {
"field": "price",
"keyed": true,
"ranges": [
{ "to": 100 },
{ "from": 100, "to": 200 },
{ "from": 200 }
]
}
}
}
} Ответ:
{
...
"aggregations": {
"price_ranges": {
"buckets": {
"*-100.0": {
"to": 100.0,
"doc_count": 2
},
"100.0-200.0": {
"from": 100.0,
"to": 200.0,
"doc_count": 2
},
"200.0-*": {
"from": 200.0,
"doc_count": 3
}
}
}
}
} Также можно настроить ключ для каждого диапазона:
resp = client.search(
index="sales",
aggs={
"price_ranges": {
"range": {
"field": "price",
"keyed": True,
"ranges": [
{
"key": "cheap",
"to": 100
},
{
"key": "average",
"from": 100,
"to": 200
},
{
"key": "expensive",
"from": 200
}
]
}
}
},
)
print(resp) response = client.search(
index: 'sales',
body: {
aggregations: {
price_ranges: {
range: {
field: 'price',
keyed: true,
ranges: [
{
key: 'cheap',
to: 100
},
{
key: 'average',
from: 100,
to: 200
},
{
key: 'expensive',
from: 200
}
]
}
}
}
}
)
puts response const response = await client.search({
index: "sales",
aggs: {
price_ranges: {
range: {
field: "price",
keyed: true,
ranges: [
{
key: "cheap",
to: 100,
},
{
key: "average",
from: 100,
to: 200,
},
{
key: "expensive",
from: 200,
},
],
},
},
},
});
console.log(response); GET sales/_search
{
"aggs": {
"price_ranges": {
"range": {
"field": "price",
"keyed": true,
"ranges": [
{ "key": "cheap", "to": 100 },
{ "key": "average", "from": 100, "to": 200 },
{ "key": "expensive", "from": 200 }
]
}
}
}
} Ответ:
{
...
"aggregations": {
"price_ranges": {
"buckets": {
"cheap": {
"to": 100.0,
"doc_count": 2
},
"average": {
"from": 100.0,
"to": 200.0,
"doc_count": 2
},
"expensive": {
"from": 200.0,
"doc_count": 3
}
}
}
}
} Сценарий
Если данные в ваших документах не совсем соответствуют тому, что вы хотите агрегировать, используйте поле runtime. Например, если вам нужно применить определённый курс валюты:
resp = client.search(
index="sales",
runtime_mappings={
"price.euros": {
"type": "double",
"script": {
"source": "\n emit(doc['price'].value * params.conversion_rate)\n ",
"params": {
"conversion_rate": 0.835526591
}
}
}
},
aggs={
"price_ranges": {
"range": {
"field": "price.euros",
"ranges": [
{
"to": 100
},
{
"from": 100,
"to": 200
},
{
"from": 200
}
]
}
}
},
)
print(resp) response = client.search(
index: 'sales',
body: {
runtime_mappings: {
'price.euros' => {
type: 'double',
script: {
source: "\n emit(doc['price'].value * params.conversion_rate)\n ",
params: {
conversion_rate: 0.835526591
}
}
}
},
aggregations: {
price_ranges: {
range: {
field: 'price.euros',
ranges: [
{
to: 100
},
{
from: 100,
to: 200
},
{
from: 200
}
]
}
}
}
}
)
puts response const response = await client.search({
index: "sales",
runtime_mappings: {
"price.euros": {
type: "double",
script: {
source:
"\n emit(doc['price'].value * params.conversion_rate)\n ",
params: {
conversion_rate: 0.835526591,
},
},
},
},
aggs: {
price_ranges: {
range: {
field: "price.euros",
ranges: [
{
to: 100,
},
{
from: 100,
to: 200,
},
{
from: 200,
},
],
},
},
},
});
console.log(response); GET sales/_search
{
"runtime_mappings": {
"price.euros": {
"type": "double",
"script": {
"source": """
emit(doc['price'].value * params.conversion_rate)
""",
"params": {
"conversion_rate": 0.835526591
}
}
}
},
"aggs": {
"price_ranges": {
"range": {
"field": "price.euros",
"ranges": [
{ "to": 100 },
{ "from": 100, "to": 200 },
{ "from": 200 }
]
}
}
}
} Вложенные агрегации
В следующем примере, помимо размещения документов в разные корзины, вычисляются статистические данные по ценам в каждом ценовом диапазоне
resp = client.search(
index="sales",
aggs={
"price_ranges": {
"range": {
"field": "price",
"ranges": [
{
"to": 100
},
{
"from": 100,
"to": 200
},
{
"from": 200
}
]
},
"aggs": {
"price_stats": {
"stats": {
"field": "price"
}
}
}
}
},
)
print(resp) response = client.search(
index: 'sales',
body: {
aggregations: {
price_ranges: {
range: {
field: 'price',
ranges: [
{
to: 100
},
{
from: 100,
to: 200
},
{
from: 200
}
]
},
aggregations: {
price_stats: {
stats: {
field: 'price'
}
}
}
}
}
}
)
puts response const response = await client.search({
index: "sales",
aggs: {
price_ranges: {
range: {
field: "price",
ranges: [
{
to: 100,
},
{
from: 100,
to: 200,
},
{
from: 200,
},
],
},
aggs: {
price_stats: {
stats: {
field: "price",
},
},
},
},
},
});
console.log(response); GET sales/_search
{
"aggs": {
"price_ranges": {
"range": {
"field": "price",
"ranges": [
{ "to": 100 },
{ "from": 100, "to": 200 },
{ "from": 200 }
]
},
"aggs": {
"price_stats": {
"stats": { "field": "price" }
}
}
}
}
} Ответ:
{
...
"aggregations": {
"price_ranges": {
"buckets": [
{
"key": "*-100.0",
"to": 100.0,
"doc_count": 2,
"price_stats": {
"count": 2,
"min": 10.0,
"max": 50.0,
"avg": 30.0,
"sum": 60.0
}
},
{
"key": "100.0-200.0",
"from": 100.0,
"to": 200.0,
"doc_count": 2,
"price_stats": {
"count": 2,
"min": 150.0,
"max": 175.0,
"avg": 162.5,
"sum": 325.0
}
},
{
"key": "200.0-*",
"from": 200.0,
"doc_count": 3,
"price_stats": {
"count": 3,
"min": 200.0,
"max": 200.0,
"avg": 200.0,
"sum": 600.0
}
}
]
}
}
} Поля гистограммы
Выполнение агрегации диапазонов по полям гистограммы вычисляет общее количество подсчётов для каждого настроенного диапазона.
Это делается без интерполяции между значениями поля гистограммы. Следовательно, возможно, что диапазон находится «между» двумя значениями гистограммы. Результирующая корзина диапазона будет иметь количество документов, равное нулю.
Вот пример выполнения агрегации диапазонов по следующему индексу, который хранит предварительно агрегированные гистограммы с метриками задержки (в миллисекундах) для разных сетей:
resp = client.indices.create(
index="metrics_index",
mappings={
"properties": {
"network": {
"properties": {
"name": {
"type": "keyword"
}
}
},
"latency_histo": {
"type": "histogram"
}
}
},
)
print(resp)
resp1 = client.index(
index="metrics_index",
id="1",
refresh=True,
document={
"network.name": "net-1",
"latency_histo": {
"values": [
1,
3,
8,
12,
15
],
"counts": [
3,
7,
23,
12,
6
]
}
},
)
print(resp1)
resp2 = client.index(
index="metrics_index",
id="2",
refresh=True,
document={
"network.name": "net-2",
"latency_histo": {
"values": [
1,
6,
8,
12,
14
],
"counts": [
8,
17,
8,
7,
6
]
}
},
)
print(resp2)
resp3 = client.search(
index="metrics_index",
size="0",
filter_path="aggregations",
aggs={
"latency_ranges": {
"range": {
"field": "latency_histo",
"ranges": [
{
"to": 2
},
{
"from": 2,
"to": 3
},
{
"from": 3,
"to": 10
},
{
"from": 10
}
]
}
}
},
)
print(resp3) response = client.indices.create(
index: 'metrics_index',
body: {
mappings: {
properties: {
network: {
properties: {
name: {
type: 'keyword'
}
}
},
latency_histo: {
type: 'histogram'
}
}
}
}
)
puts response
response = client.index(
index: 'metrics_index',
id: 1,
refresh: true,
body: {
'network.name' => 'net-1',
latency_histo: {
values: [
1,
3,
8,
12,
15
],
counts: [
3,
7,
23,
12,
6
]
}
}
)
puts response
response = client.index(
index: 'metrics_index',
id: 2,
refresh: true,
body: {
'network.name' => 'net-2',
latency_histo: {
values: [
1,
6,
8,
12,
14
],
counts: [
8,
17,
8,
7,
6
]
}
}
)
puts response
response = client.search(
index: 'metrics_index',
size: 0,
filter_path: 'aggregations',
body: {
aggregations: {
latency_ranges: {
range: {
field: 'latency_histo',
ranges: [
{
to: 2
},
{
from: 2,
to: 3
},
{
from: 3,
to: 10
},
{
from: 10
}
]
}
}
}
}
)
puts response const response = await client.indices.create({
index: "metrics_index",
mappings: {
properties: {
network: {
properties: {
name: {
type: "keyword",
},
},
},
latency_histo: {
type: "histogram",
},
},
},
});
console.log(response);
const response1 = await client.index({
index: "metrics_index",
id: 1,
refresh: "true",
document: {
"network.name": "net-1",
latency_histo: {
values: [1, 3, 8, 12, 15],
counts: [3, 7, 23, 12, 6],
},
},
});
console.log(response1);
const response2 = await client.index({
index: "metrics_index",
id: 2,
refresh: "true",
document: {
"network.name": "net-2",
latency_histo: {
values: [1, 6, 8, 12, 14],
counts: [8, 17, 8, 7, 6],
},
},
});
console.log(response2);
const response3 = await client.search({
index: "metrics_index",
size: 0,
filter_path: "aggregations",
aggs: {
latency_ranges: {
range: {
field: "latency_histo",
ranges: [
{
to: 2,
},
{
from: 2,
to: 3,
},
{
from: 3,
to: 10,
},
{
from: 10,
},
],
},
},
},
});
console.log(response3); PUT metrics_index
{
"mappings": {
"properties": {
"network": {
"properties": {
"name": {
"type": "keyword"
}
}
},
"latency_histo": {
"type": "histogram"
}
}
}
}
PUT metrics_index/_doc/1?refresh
{
"network.name" : "net-1",
"latency_histo" : {
"values" : [1, 3, 8, 12, 15],
"counts" : [3, 7, 23, 12, 6]
}
}
PUT metrics_index/_doc/2?refresh
{
"network.name" : "net-2",
"latency_histo" : {
"values" : [1, 6, 8, 12, 14],
"counts" : [8, 17, 8, 7, 6]
}
}
GET metrics_index/_search?size=0&filter_path=aggregations
{
"aggs": {
"latency_ranges": {
"range": {
"field": "latency_histo",
"ranges": [
{"to": 2},
{"from": 2, "to": 3},
{"from": 3, "to": 10},
{"from": 10}
]
}
}
}
} Агрегация range просуммирует счётчики каждого диапазона, вычисленные на основе values, и вернёт следующий результат:
{
"aggregations": {
"latency_ranges": {
"buckets": [
{
"key": "*-2.0",
"to": 2.0,
"doc_count": 11
},
{
"key": "2.0-3.0",
"from": 2.0,
"to": 3.0,
"doc_count": 0
},
{
"key": "3.0-10.0",
"from": 3.0,
"to": 10.0,
"doc_count": 55
},
{
"key": "10.0-*",
"from": 10.0,
"doc_count": 31
}
]
}
}
} Агрегация диапазонов — это агрегация по корзинам, которая разделяет документы на корзины, а не вычисляет метрики по полям, как это делают метрические агрегации. Каждая корзина представляет собой коллекцию документов, для которых могут быть выполнены вложенные агрегации. С другой стороны, поле гистограммы — это предварительно агрегированное поле, представляющее собой множество значений внутри одного поля: корзины числовых данных и количество элементов/документов для каждой корзины. Это несоответствие между ожидаемым входом агрегаций диапазонов (ожидаются исходные документы) и полем гистограммы (которое предоставляет сводную информацию) ограничивает результат агрегации только количеством документов для каждой корзины.
Следовательно, при выполнении агрегации диапазонов по полю гистограммы вложенные агрегации запрещены.
© 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-range-aggregation.html