Skip to content
ES
数据聚合

数据聚合

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 可查看此误差大小。

image-20260610121519208

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 固定毫秒数 1h30m5d
calendar_interval 日历感知(正确处理月份/时区) dayweekmonthquarteryear

💡 最佳实践:用 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" }]
                    }
                }
            }
        }
    }
}

image-20260610122653674

💡 最佳实践: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"