Skip to content

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的工作流程:

  1. 首先,每个分片都会维护一个map以及一个布谷鸟过滤器;
  2. 之后,遍历该分片上的文档;
    1. 首先判断文档中的指定字段值是否在布谷鸟过滤器中,如果在,则说明该值已经超过了阈值,则可以直接跳过;
    2. 如果文档字段值不在布谷鸟过滤器中,则判断map中是否有该值,如果有则将该值计数器+1;
    3. 如果指定的字段值第一次出现,则将其放入map,并设置计数为1;
  3. 遍历分片上的文档,当map中的某个值计数超过max_doc_count后,将该值从map中移除,并放入布谷鸟过滤器中;
  4. 最后,将每个分片的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:计算平均值;

    例如计算下面的订单价格平均值:

    json
    GET /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:查找最大值;

    例如,寻找价格最大值的订单:

    json
    GET /kibana_sample_data_ecommerce/_search
    {
      "size": 0,
      "aggs": {
        "max_price": {
          "max": {
            "field": "taxful_total_price"
          }
        }
      }
    }

    结果如下:

    json
    {
      "aggregations" : {
        "max_price" : {
          "value" : 2250.0
        }
      }
    }
  • min:查找最小值;

    例如,寻找价格最小的订单:

    json
    GET /kibana_sample_data_ecommerce/_search
    {
      "size": 0,
      "aggs": {
        "min_price": {
          "min": {
            "field": "taxful_total_price"
          }
        }
      }
    }

    结果如下:

    json
    {
      "aggregations" : {
        "min_price" : {
          "value" : 6.98828125
        }
      }
    }
  • sum:计算总和;

    例如,计算所有订单的价格总和:

    json
    GET /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的文档数,注意,该聚合不会去重;

    例如:

    json
    GET /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% 左右(取决于具体值)。

    例子:

    json
    GET /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% 的请求在这个时间以内完成(普通用户感知)
P9090分位90% 的用户体验良好,只有 10% 用户觉得有一点慢
P9595分位(最常用!)行业标准:绝大多数公司对外公布的 SLA 都是 P95
P9999分位(尾部延迟)只有 1% 的请求很慢,通常是网络抖动、超时、慢 SQL 等
P99.999.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_bucketsibling管道聚合,用于计算兄弟聚合中指定指标的平均值。注意:

  • 兄弟聚合必须是多桶聚合,例如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天
100891208812890123

那么计算某一天的营业额对于前一天的变化,就可以使用derivative管道聚合,结果如下:

第1天第2天第3天第4天第5天第6天第7天
-1131-3240-3843

假设某一天的营业额数据缺失(未开单或者数据丢失),那么数据如下:

第1天第2天第3天第4天第5天第6天第7天
100898812890123

此时如果再计算derivative,就需要处理缺失的数据,此时有两种方案:

  • 忽略:不计算缺失值,跳过:

    第1天第2天第3天第4天第5天第6天第7天
    -1140-3843
  • 默认值:将缺失的数据视为0,照常计算:

    第1天第2天第3天第4天第5天第6天第7天
    -11-898840-3843

示例如下:

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进行筛选。常见筛选方案:

排名方法适用场景说明
1bucket_selector最常用!过滤 Parent Pipeline 的桶99% 的场景都用它
2bucket_script + bucket_selector 组合需要先算一个中间值,再过滤环比、同比、增长率后过滤必备
3filter / filters aggregation聚合前就知道条件(静态条件)适合已知阈值

参考资料

[1] https://www.elastic.co/docs/reference/aggregations