3. Bucket聚合实战:terms、range、date_range、histogram、date_histogram、filter、filters
好了,咱们进入Bucket聚合的实战环节。
Bucket聚合,说白了就是「分桶」。把数据按某种规则扔进不同的桶里,然后每个桶再单独做统计。我刚开始学ES时,觉得这玩意儿跟SQL里的GROUP BY差不多。嗯,确实有相似之处,但ES的桶更灵活,能嵌套,能组合,花样多得很。
3.1 terms聚合:最常用的分组利器
terms聚合,按字段值分组。比如按城市、按状态、按商品类别分组统计。
我个人习惯,做数据看板时第一个聚合就是terms,先看看数据分布情况。
GET /orders/_search
{
"size": 0,
"aggs": {
"by_city": {
"terms": {
"field": "city.keyword",
"size": 10
}
}
}
}
返回结果里,每个城市就是一个桶,里面有个doc_count字段,告诉你这个桶里有多少文档。
size参数。但别设太大,否则性能扛不住。
我在项目中遇到过一个问题:某个字段的基数特别高(比如用户ID),用terms聚合时内存直接爆了。后来我加了"execution_hint": "map"才缓解。嗯,这里要注意,高基数字段慎用terms。
3.2 range聚合:数值范围分桶
range聚合,按数值范围分桶。比如按价格区间、按年龄区间、按分数区间。
GET /products/_search
{
"size": 0,
"aggs": {
"price_ranges": {
"range": {
"field": "price",
"ranges": [
{ "to": 100 },
{ "from": 100, "to": 500 },
{ "from": 500 }
]
}
}
}
}
这里要注意,from是包含边界,to是不包含边界。也就是说,价格100的商品会落在第二个桶里,而不是第一个。
3.3 date_range聚合:时间范围分桶
date_range跟range差不多,只不过字段类型是日期。适合按时间段统计,比如按周、按月、按季度。
GET /orders/_search
{
"size": 0,
"aggs": {
"order_dates": {
"date_range": {
"field": "order_date",
"format": "yyyy-MM-dd",
"ranges": [
{ "from": "2024-01-01", "to": "2024-03-31" },
{ "from": "2024-04-01", "to": "2024-06-30" },
{ "from": "2024-07-01", "to": "2024-09-30" }
]
}
}
}
}
日期格式一定要跟字段里的格式匹配,否则ES会报错。我建议统一用yyyy-MM-dd或者yyyy-MM-dd HH:mm:ss,别搞花里胡哨的格式。
3.4 histogram聚合:固定间隔直方图
histogram聚合,按固定间隔分桶。比如每10块钱一个区间,每5岁一个区间。
GET /products/_search
{
"size": 0,
"aggs": {
"price_histogram": {
"histogram": {
"field": "price",
"interval": 50
}
}
}
}
这个聚合特别适合做直方图可视化。你想想看,前端拿到数据直接就能画柱状图,省事得很。
3.5 date_histogram聚合:时间直方图
date_histogram是histogram的时间版本。按固定时间间隔分桶,比如每小时、每天、每月。
GET /logs/_search
{
"size": 0,
"aggs": {
"logs_over_time": {
"date_histogram": {
"field": "timestamp",
"fixed_interval": "1h"
}
}
}
}
这里有两个参数容易搞混:fixed_interval和calendar_interval。
fixed_interval:固定间隔,比如1小时就是3600秒,不管是不是整点。calendar_interval:日历间隔,比如1个月,它会按自然月来分桶。
我个人习惯,做日志分析用fixed_interval,做业务报表用calendar_interval。为什么?日志是连续的,业务是按月结算的,各取所需嘛。
3.6 filter聚合:单个条件过滤桶
filter聚合,先过滤再聚合。它只产生一个桶,桶里是满足条件的文档。
GET /orders/_search
{
"size": 0,
"aggs": {
"high_value_orders": {
"filter": {
"range": {
"total_amount": { "gte": 1000 }
}
},
"aggs": {
"avg_amount": {
"avg": { "field": "total_amount" }
}
}
}
}
}
这个聚合特别适合做「条件内再统计」。比如我想看大额订单的平均金额,用filter聚合一步到位。
3.7 filters聚合:多个条件并行桶
filters聚合是filter的升级版。它可以定义多个过滤条件,每个条件生成一个桶。
GET /orders/_search
{
"size": 0,
"aggs": {
"order_status": {
"filters": {
"filters": {
"pending": { "term": { "status": "pending" } },
"shipped": { "term": { "status": "shipped" } },
"completed": { "term": { "status": "completed" } },
"cancelled": { "term": { "status": "cancelled" } }
}
},
"aggs": {
"total_amount": {
"sum": { "field": "total_amount" }
}
}
}
}
}
你看,一个请求就把所有状态的订单金额统计出来了。前端拿到数据,直接画饼图或者柱状图,非常方便。
3.8 实战:组合使用多种Bucket聚合
真正的业务场景,很少只用一种聚合。我给大家看一个我实际做过的报表查询:
GET /orders/_search
{
"size": 0,
"aggs": {
"by_city": {
"terms": {
"field": "city.keyword",
"size": 5
},
"aggs": {
"by_status": {
"filters": {
"filters": {
"pending": { "term": { "status": "pending" } },
"completed": { "term": { "status": "completed" } }
}
},
"aggs": {
"over_time": {
"date_histogram": {
"field": "order_date",
"calendar_interval": "month"
}
}
}
}
}
}
}
}
这个查询干了三件事:
- 先按城市分组(terms)
- 每个城市里再按订单状态过滤(filters)
- 每个状态里再按月统计订单数量(date_histogram)
三层嵌套,一个请求搞定。这就是ES聚合的强大之处——可以无限嵌套,组合出你想要的任何统计维度。
- terms:按字段值分组,最常用
- range/date_range:按数值/时间范围分组
- histogram/date_histogram:按固定间隔分组,适合做图表
- filter/filters:按条件过滤后分组,适合做条件统计
嗯,Bucket聚合的内容就这些。下一章咱们聊聊Metric聚合,看看怎么在桶里做各种统计计算。