数据聚合
ES 的聚合(Aggregation)能力类似于 SQL 的 GROUP BY + 聚合函数,支持复杂的分组、统计和管道计算。
聚合三大类
聚合 (Aggregation)
├── Bucket(桶) → 分组:按某个维度将文档分到不同的"桶"
├── Metrics(度量) → 计算:对桶内的文档进行数值统计
└── Pipeline(管道)→ 二次计算:对其他聚合结果再做聚合对应 SQL 思维:
-- SQL
SELECT state, COUNT(*), AVG(age)
FROM bank
GROUP BY state
ORDER BY AVG(age) DESC;
-- ES DSL
GET /bank/_search
{
"size": 0,
"aggs": {
"by_state": {
"terms": { "field": "state.keyword" },
"aggs": {
"avg_age": { "avg": { "field": "age" } }
}
}
}
}🚨 陷阱:聚合字段必须为 keyword、数值、日期或布尔类型。
text字段已被分词,不可直接聚合——请使用.keyword子字段。
Bucket 聚合
Terms — 按字段值分组
最常用的桶聚合,类似 SQL GROUP BY。
GET /bank/_search
{
"size": 0,
"aggs": {
"by_state": {
"terms": {
"field": "state",
"size": 20,
"order": { "_count": "asc" },
"min_doc_count": 1,
"show_term_doc_count_error": true
}
}
}
}| 参数 | 说明 |
|---|---|
field |
聚合字段 |
size |
返回前 N 个桶(默认 10) |
order |
排序:_count(文档数)/ _key(字段值) |
min_doc_count |
最少文档数门槛,低于此值不返回 |
show_term_doc_count_error |
显示计数误差(分布式分片上计数的误差) |
🔬 深入原理:Terms 聚合在分布式环境中存在计数误差——每个分片返回自己的 Top N,协调节点汇总。由于数据分布不均匀,一些在全局 Top N 的桶可能在某个分片上排不进 Top N 而丢失。
show_term_doc_count_error可查看此误差大小。

Range — 自定义数值范围分组
GET /bank/_search
{
"size": 0,
"aggs": {
"age_ranges": {
"range": {
"field": "age",
"ranges": [
{ "to": 25 },
{ "from": 25, "to": 35 },
{ "from": 35, "to": 50 },
{ "from": 50 }
]
}
}
}
}Date Range — 日期范围分组
GET /logs/_search
{
"size": 0,
"aggs": {
"periods": {
"date_range": {
"field": "@timestamp",
"ranges": [
{ "to": "now-7d/d", "key": "一周前" },
{ "from": "now-7d/d", "to": "now/d", "key": "本周" }
]
}
}
}
}Date Histogram — 时间直方图
时间序列分析的基石,固定时间间隔分组。
GET /logs/_search
{
"size": 0,
"aggs": {
"orders_over_time": {
"date_histogram": {
"field": "@timestamp",
"calendar_interval": "day",
"format": "yyyy-MM-dd",
"min_doc_count": 1
}
}
}
}| 间隔类型 | 说明 | 示例 |
|---|---|---|
fixed_interval |
固定毫秒数 | 1h、30m、5d |
calendar_interval |
日历感知(正确处理月份/时区) | day、week、month、quarter、year |
💡 最佳实践:用
calendar_interval处理日/周/月等日历相关分组(正确处理 DST、月份不等长)。用fixed_interval处理固定时长场景。
Histogram — 数值直方图
GET /bank/_search
{
"size": 0,
"aggs": {
"balance_histogram": {
"histogram": {
"field": "balance",
"interval": 5000,
"min_doc_count": 1
}
}
}
}Filter — 单条件过滤桶
GET /bank/_search
{
"size": 0,
"aggs": {
"vip_users": {
"filter": { "range": { "balance": { "gte": 50000 } } },
"aggs": {
"avg_age": { "avg": { "field": "age" } }
}
},
"normal_users": {
"filter": { "range": { "balance": { "lt": 50000 } } },
"aggs": {
"avg_age": { "avg": { "field": "age" } }
}
}
}
}Filters — 多条件过滤桶
GET /bank/_search
{
"size": 0,
"aggs": {
"balance_groups": {
"filters": {
"filters": {
"low": { "range": { "balance": { "lt": 10000 } } },
"medium": { "range": { "balance": { "gte": 10000, "lt": 50000 } } },
"high": { "range": { "balance": { "gte": 50000 } } }
}
}
}
}
}Metrics 聚合
常用 Metrics 速查
| 类型 | 说明 | 返回 |
|---|---|---|
avg |
平均值 | { "value": 25000.5 } |
max / min |
最大/最小值 | { "value": 99999 } |
sum |
求和 | { "value": 1250000 } |
stats |
综合统计 | count + min + max + avg + sum |
extended_stats |
扩展统计 | stats + std_deviation + variance + 平方和 |
cardinality |
近似去重计数 | { "value": 832 } |
value_count |
非空计数 | { "value": 1000 } |
percentiles |
百分位数 | { "values": { "50.0": 32000, "99.0": 98000 } } |
top_hits |
取每个桶内的 Top N 文档 | 完整文档 |
geo_centroid |
地理坐标的中心点 | lat + lon |
Metrics 作为子聚合
GET /bank/_search
{
"size": 0,
"aggs": {
"by_state": {
"terms": {
"field": "state",
"size": 20,
"order": { "stats_age.avg": "desc" }
},
"aggs": {
"stats_age": {
"stats": { "field": "age" }
},
"sample_user": {
"top_hits": {
"size": 1,
"_source": ["firstname", "lastname"],
"sort": [{ "balance": "desc" }]
}
}
}
}
}
}
💡 最佳实践:
order中引用子聚合的语法为"子聚合名.指标"(如"stats_age.avg")。指标名须与子聚合类型返回值中的 key 一致(stats返回avg/max/min/sum/count)。
Cardinality — 近似去重
GET /logs/_search
{
"size": 0,
"aggs": {
"unique_visitors": {
"cardinality": {
"field": "user_id",
"precision_threshold": 3000
}
}
}
}🔬 深入原理:
cardinality基于 HyperLogLog++ 算法,在precision_threshold以内精度接近 100%(误差约 2%)。内存占用固定,非常适合大基数去重。
Percentiles — 百分位数
GET /bank/_search
{
"size": 0,
"aggs": {
"balance_percentiles": {
"percentiles": {
"field": "balance",
"percents": [50, 90, 95, 99]
}
}
}
}Top Hits — 每个桶内取 Top N 文档
GET /bank/_search
{
"size": 0,
"aggs": {
"by_state": {
"terms": { "field": "state" },
"aggs": {
"top_richest": {
"top_hits": {
"size": 3,
"sort": [{ "balance": "desc" }],
"_source": ["firstname", "lastname", "balance"]
}
}
}
}
}
}Pipeline 聚合
对其他聚合的输出结果做二次计算。
常用 Pipeline 类型
| 类型 | 说明 | 示例 |
|---|---|---|
derivative |
导数(环比) | 今天 vs 昨天的订单变化 |
moving_avg |
移动平均 | 7 日平滑趋势线 |
cumulative_sum |
累计求和 | 年初至今的销售额 |
bucket_sort |
对桶的元数据排序/分页 | 聚合结果按指标排序取 Top N |
bucket_script |
桶间自定义脚本 | 计算"转化率 = 下单量 / 浏览量" |
serial_diff |
序列差分 | 与前一桶的差值 |
示例:销售额环比
GET /sales/_search
{
"size": 0,
"aggs": {
"sales_per_month": {
"date_histogram": {
"field": "date",
"calendar_interval": "month"
},
"aggs": {
"total_sales": {
"sum": { "field": "amount" }
},
"sales_diff": {
"derivative": {
"buckets_path": "total_sales"
}
}
}
}
}
}示例:移动平均
GET /sales/_search
{
"size": 0,
"aggs": {
"sales_per_day": {
"date_histogram": {
"field": "date",
"calendar_interval": "day"
},
"aggs": {
"sales": { "sum": { "field": "amount" } },
"sales_ma_7": {
"moving_avg": {
"buckets_path": "sales",
"window": 7,
"model": "simple"
}
}
}
}
}
}🔬 深入原理:
buckets_path的语法使用>分隔层级,如bucket_name>metric_name。特殊值_count引用桶自身的文档数。
聚合中的分页
ES 聚合结果本身不通过 from/size 分页,但可以通过以下方式截取:
bucket_sort — 对桶排序并截取
GET /_search
{
"size": 0,
"aggs": {
"by_category": {
"terms": { "field": "category", "size": 100 },
"aggs": {
"total": { "sum": { "field": "amount" } },
"pagination": {
"bucket_sort": {
"sort": [{ "total": "desc" }],
"from": 10,
"size": 10
}
}
}
}
}
}Nested 聚合
对 nested 类型字段内部做聚合:
GET /orders/_search
{
"size": 0,
"aggs": {
"items_agg": {
"nested": { "path": "items" },
"aggs": {
"avg_price": { "avg": { "field": "items.price" } },
"by_name": { "terms": { "field": "items.name" } }
}
}
}
}常见陷阱
| 陷阱 | 说明 |
|---|---|
🚨 text 字段不可聚合 |
ES 报错或返回空,应使用 .keyword 子字段 |
| 🚨 Terms 聚合有计数误差 | 分布式的各分片独自计数后汇总,Top N 可能不精确 |
| 🚨 Terms 默认只返回 Top 10 | 需要更多时设置 size |
🚨 不设 size: 0 |
同时返回文档和聚合结果,浪费带宽与解析时间 |
🚨 cardinality 不是精确计数 |
HyperLogLog++ 算法允许约 2% 误差 |
🚨 date_histogram 用错间隔类型 |
DST 切换日不应用 fixed_interval,用 calendar_interval |
🚨 Pipeline 聚合的 buckets_path |
语法易错,格式为 子聚合名>指标名 |
| 🚨 子聚合排序语法 | 格式 "子聚合名.指标",如 "stats_age.avg" |