Appearance
Aggregations
本文介绍ES中的聚合功能(Aggregations)。
1. 聚合的基本概念及分类
聚合是指在文档数据的基础上,进行数据分析、统计和汇总的过程。
ES中聚合分为以下三类:
- 桶聚合/分组聚合(Bucket Aggregation):以文档为数据源,将文档划分为不同的组(桶),每个组包含满足特定条件的文档;
- 指标聚合(Metric Aggregation):以文档为数据源,对文档中的字段进行计算,生成统计指标,如最大值、最小值、平均值等;
- 管道聚合(Pipeline Aggregation):以其他聚合的结果为输入,进行进一步的计算和处理,适用于复杂计算;
在ES中,可以在_search API中使用aggs参数来定义聚合查询。例如:
json
GET /my_index/_search
{
"query":{
// 查询条件
},
"size": 0,
"aggs": {
"agg_name": {
// 聚合类型及参数
}
}
}query部分定义了查询条件,用于筛选文档,只有符合条件的文档才会被聚合;size参数设置为0,表示不返回文档,只返回聚合结果;aggs部分定义了聚合操作,可以包含多个聚合,每个聚合都有一个唯一的名称(如agg_name),用于标识该聚合;
2. 桶聚合
桶聚合用于将文档划分为不同的分组(桶),每个分组包含满足特定条件的文档。
2.1 terms
参考资料:https://www.elastic.co/docs/reference/aggregations/search-aggregations-bucket-terms-aggregation
Terms聚合用于根据字段的值将文档分组,类似于SQL中的GROUP BY操作,它会返回每个唯一值及其对应的文档数量。语法如下:
json
"aggs": {
"agg_name": {
"terms": {
"field": "field_name",
"size": 10
}
}
}agg_name:聚合的名称,可结合应用自定义;terms:指定使用Terms聚合;field:指定用于分组的字段名称;size:指定返回的分组数量,默认为10;
例如:
json
GET /goods_info/_search
{
"size": 0,
"aggs": {
"group_by_company": {
"terms":{
"field": "companyId"
}
}
}
}该查询会根据companyId字段对商品索引进行分组,并返回每个companyId值及其对应的商品文档数量,默认的返回前10个分组,并按照文档数量降序排列。 关于聚合部分的结果如下:
Details
json
{
"aggregations": {
"group_by_company": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 2,
"buckets": [
{
"key": 90021,
"doc_count": 130857
},
{
"key": 89222,
"doc_count": 74743
},
{
"key": 32213,
"doc_count": 52098
},
{
"key": 42123,
"doc_count": 30148
},
{
"key": 12325,
"doc_count": 25153
},
{
"key": 62431,
"doc_count": 172
},
{
"key": 12324,
"doc_count": 24
},
{
"key": 12315,
"doc_count": 19
},
{
"key": 11905,
"doc_count": 18
},
{
"key": 12105,
"doc_count": 12
}
]
}
}
}在结果中,buckets数组包含了每个分组的信息,并且按照每个分组内的文档数量降序排列:
key:表示分组的唯一值,即companyId的值;doc_count:表示该分组包含的文档数量;
ES 还返回了另外两个字段:
doc_count_error_upper_bound:表示文档数量的误差上限;在ES中,一个索引可能有多个分片,那么协调节点会把
terms聚合发送给各个分片,为了保证效率,每个分片并不会返回所有的分组,只会返回前shard_size个分组(shard_size的默认值为size * 1.5 + 10),就有可能出现某个分组在某分片上文档数量很多,在其他分片上文档数量很少,以至于排在了shard_size后,那么这些文档就不会被统计,这就是doc_count_error_upper_bound的含义。sum_other_doc_count:表示未包含在返回的分组中的文档数量总和,假设有更多的分组未返回,这个字段表示这些未返回分组中的文档总数。例如,假设总共有12个公司,那么有2个公司未包含在返回的前10个分组中,这2个公司的文档数量总和为2。
注意,terms文档提供了sort选项,但不建议按照其他顺序排序,如果要返回文档数最少的分组,使用rare_items。
2.2 rare_items
rare_items可以看作是items聚合的相反,返回文档数量较少的分组,语法如下:
json
{
"rare_terms": {
"field": "the_field",
"max_doc_count": 1,
"precision": 0.001
}
}field:指定用于分组的字段;max_doc_count:默认值为1,表示结果中的分组,文档数量都不超过max_doc_count,如果超过则不返回,最大值为100;precision:精度,用于设置rare_items聚合使用的布谷鸟过滤器的精度,默认值为0.001,最小值为0.00001;值越小表示精度越高,但也会消耗更多的内存;
rare_items的工作流程:
- 首先,每个分片都会维护一个map以及一个布谷鸟过滤器;
- 之后,遍历该分片上的文档;
- 首先判断文档中的指定字段值是否在布谷鸟过滤器中,如果在,则说明该值已经超过了阈值,则可以直接跳过;
- 如果文档字段值不在布谷鸟过滤器中,则判断map中是否有该值,如果有则将该值计数器+1;
- 如果指定的字段值第一次出现,则将其放入map,并设置计数为1;
- 遍历分片上的文档,当map中的某个值计数超过
max_doc_count后,将该值从map中移除,并放入布谷鸟过滤器中; - 最后,将每个分片的map和布谷鸟过滤器结果合并,再判断最终的结果:只有map计数结果之和不超过
max_doc_count的值并且不在布谷鸟过滤器中的值,才是最终的返回结果;
例如:
json
GET /goods_info/_search
{
"size": 0,
"aggs": {
"rare_group_by_company": {
"rare_terms":{
"field": "companyId",
"max_doc_count": 10
}
}
}
}聚合部分结果如下:
json
{
"aggregations": {
"rare_group_by_company": {
"buckets": [
{
"key": 10081,
"doc_count": 2
}
]
}
}
}2.3 range
range聚合将文档字段值按照范围分组,例如:
json
GET sales/_search
{
"aggs": {
"price_ranges": {
"range": {
"field": "price",
"ranges": [
{ "key": "cheap", "to": 100.0 },
{ "key": "average", "from": 100.0, "to": 200.0 },
{ "key": "expensive", "from": 200.0 }
]
}
}
}
}field:指定要分组的字段;ranges:用于定义多个范围分组:key:用于定义每组的名称;from:用于定义每组的起始值(包含);to:用于定义每组的结束值(不包含);
结果如下:
json
{
"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
}
}
}
}
}2.4 date range
date_range是专为日期时间类型设计的范围分组,语法如下:
json
GET /kibana_sample_data_ecommerce/_search
{
"size": 0,
"aggs": {
"sale_date_range": {
"date_range": {
"field": "order_date",
"format": "yyyy-MM-dd HH:mm:ss",
"time_zone": "+08:00",
"ranges": [
{
"from": "2025-11-09 21:59:02"
}
]
}
}
}
}field:指定要分组的字段;format:指定日期时间格式;time_zone:指定时区;ranges:指定分组范围;
结果如下:
json
{
"aggregations" : {
"sale_date_range" : {
"buckets" : [
{
"key" : "2025-11-09 21:59:02-*",
"from" : 1.762696742E12,
"from_as_string" : "2025-11-09 21:59:02",
"doc_count" : 993
}
]
}
}
}2.5 histogram
histogram聚合将数值字段的值按指定的间隔(interval)划分成多个桶(buckets),每个桶代表一个数值范围,并统计每个范围内的文档数量。
语法如下:
json
GET /kibana_sample_data_ecommerce/_search
{
"size": 0,
"aggs": {
"price_level": {
"histogram": {
"field": "taxful_total_price",
"interval": 10,
"offset": 0,
"min_doc_count": 1
}
}
}
}field:指定要分组的字段;interval:桶的间隔大小,值为正数,例如值为10,表示第一个桶的范围为[0, 10),第二个桶的范围为[10, 20)...offset:偏移量,默认值为0,即第一个桶的起始值为0,如果该值不为0,那么第一个桶的范围[offset, offset + interval);min_doc_count:最小文档数,桶中至少有多少文档才显示,默认 0(会显示空桶);
结果如下:
json
{
"aggregations" : {
"price_level" : {
"buckets" : [
{
"key" : 0.0,
"doc_count" : 3
},
{
"key" : 10.0,
"doc_count" : 35
},
// ... 省略部分结果
{
"key" : 2250.0,
"doc_count" : 1
}
]
}
}
}上述结果表示总价在[0, 10)之间的订单有3个,总价在[10, 20)之间的订单有35个...总价在[2250, 2260)之间的订单有1个。
histogram
2.6 filter与filters
对于参与聚合的文档,我们可以在_search API 中指定query过滤条件,只有满足查询条件的文档才会进行聚合计算,这是全局文档过滤。
也可以使用filter聚合定义单桶,满足filter条件的文档会组成一个桶,例如:
json
GET /kibana_sample_data_ecommerce/_search
{
"size": 0,
"aggs": {
"elyssa_order_count": {
"filter": {
"term": {
"user": "elyssa"
}
}
}
}
}结果如下:
json
{
"aggregations" : {
"elyssa_order_count" : {
"doc_count" : 348
}
}
}如果要根据多个条件组成多个分组,例如,elyssa的订单分为1组,mary的订单分为另1组,我们可以使用filters聚合:
json
GET /kibana_sample_data_ecommerce/_search
{
"size": 0,
"aggs": {
"specific_user_group": {
"filters": {
"filters": {
"elyssa": {
"term": {
"user": "elyssa"
}
},
"mary":{
"term": {
"user": "mary"
}
}
}
}
}
}
}注意有两层filters。
结果如下:
json
{
"aggregations" : {
"specific_user_group" : {
"buckets" : {
"elyssa" : {
"doc_count" : 348
},
"mary" : {
"doc_count" : 154
}
}
}
}
}3. 指标聚合
指标聚合是在文档集合基础上,计算统计值,例如最大值、最小值、平均值等。
3.1 avg/max/min/sum
avg:计算平均值;例如计算下面的订单价格平均值:
jsonGET /kibana_sample_data_ecommerce/_search { "size": 0, "aggs": { "avg_price": { "avg": { "field": "taxful_total_price", "missing": 0 } } } }missing:默认情况下,如果某个文档缺失计算字段,那么会忽略,可以使用missing为缺失值指定默认值;
结果如下:
json{ "aggregations" : { "avg_price" : { "value" : 75.05542864304813 } } }如果要计算加权平均数,查看
weighted_avg聚合。max:查找最大值;例如,寻找价格最大值的订单:
jsonGET /kibana_sample_data_ecommerce/_search { "size": 0, "aggs": { "max_price": { "max": { "field": "taxful_total_price" } } } }结果如下:
json{ "aggregations" : { "max_price" : { "value" : 2250.0 } } }min:查找最小值;例如,寻找价格最小的订单:
jsonGET /kibana_sample_data_ecommerce/_search { "size": 0, "aggs": { "min_price": { "min": { "field": "taxful_total_price" } } } }结果如下:
json{ "aggregations" : { "min_price" : { "value" : 6.98828125 } } }sum:计算总和;例如,计算所有订单的价格总和:
jsonGET /kibana_sample_data_ecommerce/_search { "size": 0, "aggs": { "sum_price": { "sum": { "field": "taxful_total_price", "missing": 0 } } } }结果如下:
json{ "aggregations" : { "sum_price" : { "value" : 350884.12890625 } } }
3.2 stats
stats聚合用于统计某个字段的平均值avg、最小值min、最大值max、总和sum以及计数count,例如:
json
GET /kibana_sample_data_ecommerce/_search
{
"size": 0,
"aggs": {
"stats_agg": {
"stats": {
"field": "taxful_total_price"
}
}
}
}结果如下:
json
{
"aggregations" : {
"stats_agg" : {
"count" : 4674,
"min" : 6.98828125,
"max" : 2250.0,
"avg" : 75.05394285542361,
"sum" : 350802.12890625
}
}
}注意,如果某文档中字段值为null,则不会统计该文档,可以使用missing配置缺失值。
3.3 extended stats
extended_stats聚合在stats的基础上,计算了方差、标准差等统计数据,例如:
json
GET /kibana_sample_data_ecommerce/_search
{
"size": 0,
"aggs": {
"extended_stats_agg": {
"extended_stats": {
"field": "taxful_total_price"
}
}
}
}结果如下:
json
{
"aggregations" : {
"extended_stats_agg" : {
"count" : 4674,
"min" : 6.98828125,
"max" : 2250.0,
"avg" : 75.05394285542361,
"sum" : 350802.12890625,
"sum_of_squares" : 3.9361025294174194E7,
"variance" : 2788.1776546177916,
"variance_population" : 2788.1776546177916,
"variance_sampling" : 2788.774311509428,
"std_deviation" : 52.80319739009932,
"std_deviation_population" : 52.80319739009932,
"std_deviation_sampling" : 52.8088469056978,
"std_deviation_bounds" : {
"upper" : 180.66033763562226,
"lower" : -30.552451924775028,
"upper_population" : 180.66033763562226,
"lower_population" : -30.552451924775028,
"upper_sampling" : 180.6716366668192,
"lower_sampling" : -30.563750955971983
}
}
}
}3.4 string stats
string_stats是一个多值指标聚合,针对于keyword类型字段,返回以下信息:
count:非null字符串的数量;min_length:最短字符串长度;max_length:最长字符串长度;avg_length:平均字符串长度;entropy:香农熵,定量地衡量一个随机变量(或信息源)的不确定性程度,或者说它平均包含多少“信息量”,如果该值越大,表示字段包含的不同值越多;
例子:
json
GET /kibana_sample_data_ecommerce/_search
{
"size": 0,
"aggs": {
"string_stats_agg": {
"string_stats": {
"field":"customer_full_name.keyword"
}
}
}
}结果如下:
json
{
"aggregations" : {
"string_stats_agg" : {
"count" : 4675,
"min_length" : 7,
"max_length" : 25,
"avg_length" : 13.309304812834224,
"entropy" : 4.773147238719484
}
}
}3.5 value_count 与 cardinality
value_count:用于统计某个字段值不为null的文档数,注意,该聚合不会去重;例如:
jsonGET /user/_search?size=0 { "aggs": { "demo": { "value_count": { "field": "address.keyword" } } } }结果如下:
json{ "aggregations" : { "demo" : { "value" : 4 } } }cardinality:基数统计(近似去重计数),注意,该聚合是近似统计;cardinality 聚合用的是 HyperLogLog++(HLL) 算法,它是一种概率性基数统计算法,特点是:
- 用极少的固定内存(几 KB 到几十 KB)就能估算出非常大的去重数量(亿级甚至十亿级);
- 结果不是 100% 精确,而是“近似值 + 很小的标准误差”;
重要参数
precision_threshold,用于控制精度阈值,默认值为3000,最大值为40000,如果设置了大于40000的值,那么与设置为40000效果相同。含义如下:当去重基数(真实 distinct 数量)小于或等于 precision_threshold 时,误差极小,通常在 ±1% 以内,甚至接近 0(几乎精确)。
当真实基数远大于 precision_threshold 时,误差会慢慢变大,但相对误差仍控制在 ±2% ~ ±5% 左右(取决于具体值)。
例子:
jsonGET /user/_search?size=0 { "aggs": { "demo": { "cardinality": { "field": "address.keyword", "precision_threshold": 100 } } } }结果如下:
json{ "aggregations" : { "demo" : { "value" : 3 } } }
3.6 percentiles
percentiles聚合计算多个百分位数。
Percentile(百分位数) 就是“排序后,有多少百分比的数据比它小(或相等)”,例如95th percentile(P95),意思位排序后,有 95% 的数据 ≤ 这个值,只有5%的数据大于这个值(说明更大、更慢、更贵...);
例如,以接口响应时间为例:
| 百分位 | 典型场景 | 实际意义(以接口响应时间为例) |
|---|---|---|
| P50 | 中位数响应时间 | 50% 的请求在这个时间以内完成(普通用户感知) |
| P90 | 90分位 | 90% 的用户体验良好,只有 10% 用户觉得有一点慢 |
| P95 | 95分位(最常用!) | 行业标准:绝大多数公司对外公布的 SLA 都是 P95 |
| P99 | 99分位(尾部延迟) | 只有 1% 的请求很慢,通常是网络抖动、超时、慢 SQL 等 |
| P99.9 | 99.9分位(极慢请求) | 通常是严重故障、单机雪崩、热点、GC 长时间停顿等 |
| P99.99 | 几乎没人用 | 金融、支付、高可用系统才关心 |
示例如下:
json
GET /kibana_sample_data_ecommerce/_search
{
"size": 0,
"aggs": {
"rcentiles_agg": {
"percentiles": {
"field": "taxful_total_price"
}
}
}
}json
{
"aggregations" : {
"rcentiles_agg" : {
"values" : {
"1.0" : 21.984375,
"5.0" : 27.984375,
"25.0" : 44.96875,
"50.0" : 63.96875,
"75.0" : 93.375,
"95.0" : 156.0,
"99.0" : 222.0
}
}
}
}其中P95的值为156.0,说明有95%的订单金额都小于等于156,只有 5%的订单金额大于156。
我们可以使用percents设置想要的百分位数:
json
GET /kibana_sample_data_ecommerce/_search
{
"size": 0,
"aggs": {
"rcentiles_agg": {
"percentiles": {
"field": "taxful_total_price",
"percents": [95, 99]
}
}
}
}注意,Elasticsearch 中的 percentiles聚合结果是近似的,不是 100% 精确的。
ES默认使用下面两种近似算法计算百分位数:
| 算法 | 默认? | 精度 | 内存占用 | 适用场景 |
|---|---|---|---|---|
| TDigest(推荐) | 是(6.x 以后默认) | 非常高(日常看不出误差) | 低(几 KB 到几十 KB) | 99.9% 的业务场景(P95、P99、P99.9) |
| HDR Histogram | 否(需手动开启) | 更高(可控小数位精度) | 高(几十 KB 到几 MB) | 对精度有强迫症的场景(如金融风控) |
在生产上推荐写法:
json
{
"latency_p95_p99": {
"percentiles": {
"field": "response_time_ms",
"percents": [90, 95, 99, 99.9],
"tdigest": {
"compression": 500 // 绝大多数公司都是 300~1000
}
}
}
}4. 桶聚合与指标聚合
桶聚合中还可以有子聚合(桶聚合或指标聚合),例如,对于某个年级的学生,我们按照班级进行桶聚合,然后计算每个班语文成绩的统计数据,这就需要桶聚合+指标聚合,例如:
json
GET /students/_search
{
"size": 0,
"query": {
"term": {
"grade": {
"value": "高一" // 假设查询“高一”这个年级
}
}
},
"aggs": {
"by_class": {
"terms": {
"field": "class_name", // 按照班级进行桶聚合
"size": 100 // 返回前100个班级,如果班级数多请调整
},
"aggs": {
"chinese_score_stats": {
"stats": {
"field": "chinese_score" // 在每个班级的桶内计算语文成绩的统计数据
}
}
}
}
}
}结果如下:
json
{
"aggregations": {
"by_class": {
"buckets": [
{
"key": "一班", // 班级名称
"doc_count": 50, // 该班级的学生人数
"chinese_score_stats": {
"count": 50, // 有语文成绩记录的学生数
"min": 65.0, // 语文最低分
"max": 98.5, // 语文最高分
"avg": 85.2, // 语文平均分
"sum": 4260.0 // 语文总分
}
},
{
"key": "二班", // 另一个班级
"doc_count": 45,
"chinese_score_stats": {
// ... 二班的统计数据
}
}
// ... 更多班级
]
}
}
}5. 管道聚合
5.1 管道聚合概述
管道聚合是作用于其他聚合结果的聚合,也就是聚合的聚合,它不看原始文档,而是拿前面聚合算出来的数字,再继续加工。
管道聚合可以分为两类:
- parent 管道聚合:写在某个桶(分组)内,聚合结果也在该桶内,作为这个桶的一个子指标;
- sibling 管道聚合:写在某个层级旁边,和普通聚合平级,聚合结果和普通聚合结果并列;
管道聚合有一个重要的属性buckets_path,用来引用其他聚合结果,具体语法如下:
聚合名称:直接写聚合名称;>:用于多层聚合,例如by_class>chinese_score_stats,表示班级桶聚合下的语文成绩统计聚合;.:用于指定某个指标聚合下的字段,例如by_class>chinese_score_stats.avg,表示班级桶聚合下的语文成绩统计聚合中的平均成绩;['key_name']:用于指定多桶聚合结果中的某个桶,如果某个指标聚合聚合返回的字段名称包含.,也可以使用[字段名称]来指定指标聚合下的字段,例如percentiles中的99.9,如果写成my-percentiles.99.9就会造成歧义,可以写成my-percentiles[99.9];
在管道聚合中,还有两个特殊路径:
_count:用来表示某个桶内的文档数;_bucket_count:用来表示某个多桶聚合返回的桶数(分组个数),即分了多少个组;
注意,buckets_path是相对路径,而不是绝对路径。
5.2 average bucket
avg_bucket是sibling管道聚合,用于计算兄弟聚合中指定指标的平均值。注意:
- 兄弟聚合必须是多桶聚合,例如
terms; - 指标必须是数值类型;
示例如下:
json
POST /student/_search
{
"size": 0,
"aggs": {
"group_by_class": {
"terms": {
"field": "class"
},
"aggs": {
"chinese_avg_score": {
"avg": {
"field": "chinese_score"
}
}
}
},
"all_avg": {
"avg_bucket": {
"buckets_path": "group_by_class>chinese_avg_score",
"format": "#,##0.00;(#,##0.00)"
}
}
}
}上述案例将学生按照班级分类,并计算每个班级的语文平均成绩,然后使用avg_bucket计算所有班级的平均成绩。
buckets_path:指定要作用的聚合路径;format:可选的,如果指定了,那么将会在结果中以value_as_string字段返回特定格式的结果;
结果如下:
json
{
"aggregations": {
"group_by_class": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 0,
"buckets": [
{
"key": "三班",
"doc_count": 3,
"chinese_avg_score": {
"value": null
}
},
{
"key": "二班",
"doc_count": 3,
"chinese_avg_score": {
"value": 90.33333333333333
}
},
{
"key": "一班",
"doc_count": 2,
"chinese_avg_score": {
"value": 96.5
}
}
]
},
"all_avg": {
"value": 93.41666666666666,
"value_as_string": "93.42"
}
}
}5.3 其他统计管道聚合
除了avg_bucket管道聚合,还有其他统计管道聚合:
max_bucket:获取多桶聚合中的某个特性最大值;min_bucket:获取多桶聚合中某个特性最小值;sum_bucket:获取多桶聚合中某个特性值的总和;stats_bucket:获取多桶聚合中某个特性值的平均值、最大值、最小值、总和和计数;extended_stats_bucket:除了stats_bucket,还返回平方和、标准差等值;
例子如下:
json
{
"size": 0,
"aggs": {
"group_by_class": {
"terms": {
"field": "class"
},
"aggs": {
"chinese_avg_score": {
"avg": {
"field": "chinese_score"
}
}
}
},
"all_avg": {
"avg_bucket": {
"buckets_path": "group_by_class>chinese_avg_score",
"format": "#,##0.00;(#,##0.00)"
}
},
"max_avg":{
"max_bucket":{
"buckets_path": "group_by_class>chinese_avg_score"
}
},
"min_person_count":{
"min_bucket":{
"buckets_path": "group_by_class._count"
}
},
"sum_person":{
"sum_bucket":{
"buckets_path": "group_by_class._count"
}
},
"stats_data":{
"stats_bucket":{
"buckets_path": "group_by_class>chinese_avg_score"
}
},
"extended_stats_data":{
"extended_stats_bucket":{
"buckets_path": "group_by_class>chinese_avg_score"
}
}
}
}TIP
注意,要访问桶内文档数,使用_count,不要使用doc_count。
结果如下:
Details
json
{
"aggregations": {
"group_by_class": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 0,
"buckets": [
{
"key": "三班",
"doc_count": 3,
"chinese_avg_score": {
"value": null
}
},
{
"key": "二班",
"doc_count": 3,
"chinese_avg_score": {
"value": 90.33333333333333
}
},
{
"key": "一班",
"doc_count": 2,
"chinese_avg_score": {
"value": 96.5
}
}
]
},
"all_avg": {
"value": 93.41666666666666,
"value_as_string": "93.42"
},
"max_avg": {
"value": 96.5,
"keys": [
"一班"
]
},
"min_person_count": {
"value": 2,
"keys": [
"一班"
]
},
"sum_person": {
"value": 8
},
"stats_data": {
"count": 2,
"min": 90.33333333333333,
"max": 96.5,
"avg": 93.41666666666666,
"sum": 186.83333333333331
},
"extended_stats_data": {
"count": 2,
"min": 90.33333333333333,
"max": 96.5,
"avg": 93.41666666666666,
"sum": 186.83333333333331,
"sum_of_squares": 17472.36111111111,
"variance": 9.506944444445253,
"variance_population": 9.506944444445253,
"variance_sampling": 19.013888888890506,
"std_deviation": 3.0833333333334645,
"std_deviation_population": 3.0833333333334645,
"std_deviation_sampling": 4.360491817317229,
"std_deviation_bounds": {
"upper": 99.58333333333358,
"lower": 87.24999999999973,
"upper_population": 99.58333333333358,
"lower_population": 87.24999999999973,
"upper_sampling": 102.13765030130111,
"lower_sampling": 84.6956830320322
}
}
}
}5.4 bucket sort
这是一个parent 管道聚合,用于对前多桶聚合的结果进行排序。
示例如下,统计每天的销售额(按周一、周二...维度):
json
GET /kibana_sample_data_ecommerce/_search
{
"size":0,
"aggs":{
"group_by_order_week":{
"terms":{
"field":"day_of_week"
},
"aggs":{
"sales_amount":{
"sum":{
"field":"taxful_total_price"
}
},
"order_by_sales_amount":{
"bucket_sort":{
"sort":[
{"sales_amount":"desc"}
],
"from": 0,
"size": 3
}
}
}
}
}
}sort:用于指定排序规则,是一个数组;排序字段可以使用buckets_path;from:指定排序后返回结果的起始值;size:指定排序后返回多少结果;
结果如下:
json
{
"aggregations" : {
"group_by_order_week" : {
"doc_count_error_upper_bound" : 0,
"sum_other_doc_count" : 0,
"buckets" : [
{
"key" : "Friday",
"doc_count" : 770,
"sales_amount" : {
"value" : 58215.58984375
}
},
{
"key" : "Thursday",
"doc_count" : 775,
"sales_amount" : {
"value" : 57807.375
}
},
{
"key" : "Saturday",
"doc_count" : 736,
"sales_amount" : {
"value" : 53841.03515625
}
}
]
}
}
}5.5 derivative
derivative用于度量指定指标的变化率,这是一个parent管道聚合。
例如,现在按照天数计算营业额
| 第1天 | 第2天 | 第3天 | 第4天 | 第5天 | 第6天 | 第7天 |
|---|---|---|---|---|---|---|
| 100 | 89 | 120 | 88 | 128 | 90 | 123 |
那么计算某一天的营业额对于前一天的变化,就可以使用derivative管道聚合,结果如下:
| 第1天 | 第2天 | 第3天 | 第4天 | 第5天 | 第6天 | 第7天 |
|---|---|---|---|---|---|---|
| -11 | 31 | -32 | 40 | -38 | 43 |
假设某一天的营业额数据缺失(未开单或者数据丢失),那么数据如下:
| 第1天 | 第2天 | 第3天 | 第4天 | 第5天 | 第6天 | 第7天 |
|---|---|---|---|---|---|---|
| 100 | 89 | 88 | 128 | 90 | 123 |
此时如果再计算derivative,就需要处理缺失的数据,此时有两种方案:
忽略:不计算缺失值,跳过:
第1天 第2天 第3天 第4天 第5天 第6天 第7天 -11 40 -38 43 默认值:将缺失的数据视为0,照常计算:
第1天 第2天 第3天 第4天 第5天 第6天 第7天 -11 -89 88 40 -38 43
示例如下:
json
GET /kibana_sample_data_ecommerce/_search
{
"query": {
"bool": {
"must_not": [
{
"range": {
"order_date": {
"gte": "2025-11-11",
"lt": "2025-11-12"
}
}
}
]
}
},
"size": 0,
"aggs": {
"group_by_month": {
"date_histogram": {
"field": "order_date",
"interval": "day"
},
"aggs": {
"sales_amount": {
"sum": {
"field": "taxful_total_price"
}
},
"sales_amount_derivative":{
"derivative": {
"buckets_path": "sales_amount",
"gap_policy": "insert_zeros"
}
}
}
}
}
}query:不查询2025-11-11日的数据,模拟数据丢失;gap_policy:将缺失值设置为默认值0;
部分结果如下:
json
{
"aggregations" : {
"group_by_month" : {
"buckets" : [
{
"key_as_string" : "2025-11-10T00:00:00.000Z",
"key" : 1762732800000,
"doc_count" : 153,
"sales_amount" : {
"value" : 12574.015625
},
"sales_amount_derivative" : {
"value" : -521.59375
}
},
{
"key_as_string" : "2025-11-11T00:00:00.000Z",
"key" : 1762819200000,
"doc_count" : 0,
"sales_amount" : {
"value" : 0.0
},
"sales_amount_derivative" : {
"value" : -12574.015625
}
},
{
"key_as_string" : "2025-11-12T00:00:00.000Z",
"key" : 1762905600000,
"doc_count" : 160,
"sales_amount" : {
"value" : 12117.65625
},
"sales_amount_derivative" : {
"value" : 12117.65625
}
}
]
}
}
}5.6 serial_diff
derivative是只能计算当前桶相较于上一个桶的变化,而serial_diff可以指定当前桶相较于前多少个桶的变化,可用于计算同比、环比。
同比:同比是以上年同期为基期相比较,即本期某一时间段(点)与上年某一时间段(点)相比,可以理解为今年第n月与去年第n月的比较。如,2022年12月份与2021年12月份相比较,2022年上半年与2021年上半年相比较就是同比。同比增长率是指本期和上一年同期相比较的增长率,计算公式为:同比增长率=(本期数-同期数)/同期数×100%。例如,某公司2022年上半年利润3000万元,为本期数,上年同期数是2021年上半年的利润2000万元,同比增长率为(3000-2000)/2000×100%=50%,即某公司2022年上半年利润同比增长50%。
除了以年为差值单位进行比较,还可以以周、月为差值单位进行比较,例如本月1号与上月1号,本周周一与上周周一进行比较;
环比:环比是与上一个相邻统计周期相比较,表明统计指标逐期的发展变化,可以理解为第n月与第n-1月的比较。如,2022年12月份与2022年11月份相比较,2022年1月份与2021年12月份相比较就是环比。环比增长率是指本期和上期相比较的增长率,计算公式为:环比增长率=(本期数-上期数)/上期数×100%。例如,某公司2022年6月份营业收入额为100万元,为本期数,上期数是2022年5月份营业额80万元,环比增长率为(100-80)/80×100%=25%,即某公司2022年6月份营业额环比增长25%。
同比与环比解释来源:https://www.stats.gov.cn/zs/tjws/tjbk/202301/t20230101_1912960.html
示例:
json
GET /kibana_sample_data_ecommerce/_search
{
"size": 0,
"aggs": {
"group_by_month": {
"date_histogram": {
"field": "order_date",
"interval": "day"
},
"aggs": {
"sales_amount": {
"sum": {
"field": "taxful_total_price"
}
},
"sales_amount_mom":{
"serial_diff": {
"buckets_path": "sales_amount",
"lag":1,
"gap_policy": "insert_zeros"
}
},
"sales_7d_diff":{
"serial_diff": {
"buckets_path": "sales_amount",
"lag": 7,
"gap_policy": "insert_zeros"
}
}
}
}
}
}sales_amount_mom:计算环比,类似于derivative;sales_7d_diff:计算周同比,即某天与7天前的数据,有了多少变化;
部分结果如下:
json
{
"key_as_string" : "2025-11-15T00:00:00.000Z",
"key" : 1763164800000,
"doc_count" : 142,
"sales_amount" : {
"value" : 11135.03125
},
"sales_amount_mom" : {
"value" : -786.0859375
},
"sales_7d_diff" : {
"value" : 77.625
}
}5.7 cumulative_sum
cumulative_sum管道聚合用于计算累计值,例如:
json
GET /kibana_sample_data_ecommerce/_search
{
"query": {
"range": {
"order_date": {
"gte": "2025-11-01",
"lt": "2025-11-04"
}
}
},
"size": 0,
"aggs": {
"group_by_day": {
"date_histogram": {
"field": "order_date",
"interval": "day"
},
"aggs": {
"sales_amount": {
"sum": {
"field": "taxful_total_price"
}
},
"sales_amount_cumulative":{
"cumulative_sum": {
"buckets_path": "sales_amount"
}
}
}
}
}
}结果如下:
json
{
"aggregations" : {
"group_by_day" : {
"buckets" : [
{
"key_as_string" : "2025-11-01T00:00:00.000Z",
"key" : 1761955200000,
"doc_count" : 157,
"sales_amount" : {
"value" : 11480.33203125
},
"sales_amount_cumulative" : {
"value" : 11480.33203125
}
},
{
"key_as_string" : "2025-11-02T00:00:00.000Z",
"key" : 1762041600000,
"doc_count" : 158,
"sales_amount" : {
"value" : 11533.265625
},
"sales_amount_cumulative" : {
"value" : 23013.59765625
}
},
{
"key_as_string" : "2025-11-03T00:00:00.000Z",
"key" : 1762128000000,
"doc_count" : 144,
"sales_amount" : {
"value" : 10417.8125
},
"sales_amount_cumulative" : {
"value" : 33431.41015625
}
}
]
}
}
}5.8 bucket_script
bucket_script可以在上游聚合结果桶中进行脚本计算,注意选择进行计算的指标必须为数值类型,脚本返回值也应该是数值类型。
例如,计算2025年11月来自加利福利亚的订单金额,占11月总金额的比例:
json
GET /kibana_sample_data_ecommerce/_search
{
// 筛选出11月的订单
"query": {
"range": {
"order_date": {
"gte": "2025-11-01",
"lt": "2025-12-01"
}
}
},
"size": 0,
"aggs": {
// 按照月分组,由于已经有了query,这里只会分1个组
"11th_month_order": {
"date_histogram": {
"field": "order_date",
"interval": "month"
},
"aggs": {
// 计算11月订单总金额
"total_amount": {
"sum": {
"field": "taxful_total_price"
}
},
// 计算来自加利福利亚的订单金额
"california_amount":{
// filter桶聚合
"filter": {
"term": {
"geoip.region_name": "California"
}
},
"aggs": {
// 在filter桶聚合中,计算总金额(即来自加利福利亚的订单总金额)
"amount_":{
"sum": {
"field": "taxful_total_price"
}
}
}
},
// 计算加利福利亚订单金额占比
"percent_":{
"bucket_script": {
// 指定buckets_path,注意,这里可以以kv形式指定
"buckets_path": {
"total":"total_amount",
"california_amount":"california_amount>amount_"
},
// 计算脚本
"script": "params.california_amount / params.total * 100",
"format": "#0.00"
}
}
}
}
}
}结果如下:
json
{
"aggregations" : {
"11th_month_order" : {
"buckets" : [
{
"key_as_string" : "2025-11-01T00:00:00.000Z",
"key" : 1761955200000,
"doc_count" : 2312,
"california_amount" : {
"doc_count" : 163,
"amount_" : {
"value" : 12810.015625
}
},
"total_amount" : {
"value" : 174703.15234375
},
"percent_" : {
"value" : 7.332446755050369,
"value_as_string" : "7.33"
}
}
]
}
}
}5.9 bucket_selector
在聚合后,如果我们想对聚合结果进行筛选,例如,只保留销售额大于10万的日期,我们就可以使用bucket_selector管道聚合了。
bucket_selector的脚本需要返回布尔值。
语法如下:
json
{
"bucket_selector": {
"buckets_path": {
"my_var1": "the_sum",
"my_var2": "the_value_count"
},
"script": "params.my_var1 > params.my_var2"
}
}例如,将订单按照地区进行分组,然后筛选出订单总额大于4万的地区,最后按照订单总额升序排序:
json
GET /kibana_sample_data_ecommerce/_search
{
"size": 0,
"aggs": {
"by_region": {
"terms": {
"field": "geoip.region_name",
"size": 100
},
"aggs": {
"sales_amount":{
"sum": {
"field": "taxful_total_price"
}
},
// 筛选订单
"order_over_40k": {
"bucket_selector": {
"buckets_path": {
"sales_amount": "sales_amount"
},
"script": "params.sales_amount > 40000"
}
},
// 按照订单金额升序排序
"amount_sort":{
"bucket_sort": {
"sort": [
{"sales_amount":"asc"}
]
}
}
}
}
}
}结果如下:
json
{
"aggregations" : {
"by_region" : {
"doc_count_error_upper_bound" : 0,
"sum_other_doc_count" : 0,
"buckets" : [
{
"key" : "Cairo Governorate",
"doc_count" : 491,
"sales_amount" : {
"value" : 41095.3828125
}
},
{
"key" : "New York",
"doc_count" : 896,
"sales_amount" : {
"value" : 67411.0390625
}
}
]
}
}
}出了单独使用bucket_selector,还可以结合bucket_script进行筛选。常见筛选方案:
| 排名 | 方法 | 适用场景 | 说明 |
|---|---|---|---|
| 1 | bucket_selector | 最常用!过滤 Parent Pipeline 的桶 | 99% 的场景都用它 |
| 2 | bucket_script + bucket_selector 组合 | 需要先算一个中间值,再过滤 | 环比、同比、增长率后过滤必备 |
| 3 | filter / filters aggregation | 聚合前就知道条件(静态条件) | 适合已知阈值 |