2. Metric聚合实战:avg、sum、min、max、value_count、cardinality、stats、extended_stats
好,咱们直接进入正题。Metric聚合,说白了就是做数学计算的。你想想看,我们存了那么多数据到ES里,总不能光查出来看看吧?总得算个平均值、加个总数什么的。这一章我就带你挨个过一遍最常用的几个Metric聚合。
2.1 avg聚合——算平均值
这个最简单,也最常用。比如你想知道所有订单的平均金额,直接上avg。
GET /orders/_search
{
"size": 0,
"aggs": {
"avg_price": {
"avg": {
"field": "price"
}
}
}
}
返回结果里会有一个value字段,就是平均值。嗯,这里要注意一点:如果字段里有空值,ES默认会忽略掉,不会把它算成0。我个人习惯是提前确认一下数据质量,别到时候算出来一个莫名其妙的数。
missing参数:"missing": 0。
2.2 sum聚合——求和
这个更直接,就是把所有值加起来。我在项目中遇到过这样一个场景:统计某个时间段内所有订单的总销售额。用sum聚合,几毫秒就出结果了。
GET /orders/_search
{
"size": 0,
"aggs": {
"total_revenue": {
"sum": {
"field": "price"
}
}
}
}
你可能会问:那如果字段是字符串类型怎么办?ES会报错。所以建索引的时候,数值字段一定要用integer、float、double这些类型。
2.3 min和max聚合——最小值和最大值
这两个放一起说,因为用法几乎一样。一个找最小,一个找最大。
GET /orders/_search
{
"size": 0,
"aggs": {
"min_price": {
"min": {
"field": "price"
}
},
"max_price": {
"max": {
"field": "price"
}
}
}
}
我曾经用这个组合排查过一个数据异常的问题。当时发现某天的订单最低价是负数,一查才知道是系统bug导致退款金额被错误地记录成了订单金额。你看,min和max不光能算数,还能帮你做数据质量检查。
2.4 value_count聚合——统计文档数
这个聚合统计的是某个字段有值的文档数量。注意,不是总文档数,是字段非空的文档数。
GET /orders/_search
{
"size": 0,
"aggs": {
"price_count": {
"value_count": {
"field": "price"
}
}
}
}
为什么需要这个?举个例子:你有一个用户表,其中phone字段是可选的。你想知道有多少用户填了手机号,用value_count就对了。
_count,那是统计所有文档数,包括字段为空的。这两个别搞混了。
2.5 cardinality聚合——去重计数
这个就厉害了。它类似于SQL里的COUNT(DISTINCT)。比如你想知道有多少个不同的用户下了单:
GET /orders/_search
{
"size": 0,
"aggs": {
"unique_users": {
"cardinality": {
"field": "user_id"
}
}
}
}
说实话,cardinality在数据量大的时候性能会有点吃紧。ES内部用的是HyperLogLog算法,是个近似值,不是精确的。但好处是内存占用很小。我一般会设置一个precision_threshold参数,比如10000,这样在精度和性能之间取个平衡。
GET /orders/_search
{
"size": 0,
"aggs": {
"unique_users": {
"cardinality": {
"field": "user_id",
"precision_threshold": 10000
}
}
}
}
2.6 stats聚合——一键五连
如果你觉得一个个写avg、sum、min、max、count太麻烦,ES提供了一个打包好的聚合——stats。一次请求,五个结果全出来。
GET /orders/_search
{
"size": 0,
"aggs": {
"price_stats": {
"stats": {
"field": "price"
}
}
}
}
返回结果长这样:
| 字段 | 含义 |
|---|---|
| count | 非空文档数 |
| min | 最小值 |
| max | 最大值 |
| avg | 平均值 |
| sum | 总和 |
我个人特别喜欢用这个,尤其是在做数据探索阶段。一条查询下去,数据的整体分布就心里有数了。
2.7 extended_stats聚合——加强版stats
extended_stats是在stats的基础上,额外增加了更多统计指标:
- sum_of_squares:平方和
- variance:方差
- std_deviation:标准差
- std_deviation_bounds:标准差范围(默认是±2个标准差)
GET /orders/_search
{
"size": 0,
"aggs": {
"price_extended_stats": {
"extended_stats": {
"field": "price"
}
}
}
}
返回结果里会多出这些字段。标准差这个东西,说白了就是看数据波动大不大。我曾经用这个分析过服务器响应时间,发现标准差特别大,说明有的请求特别慢,有的特别快。后来一查,是缓存命中率不稳定导致的。
extended_stats非常有用。比如你想设置一个告警规则:当响应时间超过均值+2倍标准差时,触发告警。这个聚合直接给你算好了。
2.8 综合实战:一个完整的报表查询
最后,我给你看一个我在项目中实际用过的查询。当时需要做一个销售数据看板,要求展示:总订单数、总销售额、平均客单价、最高单笔金额、最低单笔金额、以及不同用户的去重数。
GET /orders/_search
{
"size": 0,
"aggs": {
"order_count": {
"value_count": {
"field": "order_id"
}
},
"total_sales": {
"sum": {
"field": "price"
}
},
"avg_order": {
"avg": {
"field": "price"
}
},
"max_order": {
"max": {
"field": "price"
}
},
"min_order": {
"min": {
"field": "price"
}
},
"unique_users": {
"cardinality": {
"field": "user_id",
"precision_threshold": 10000
}
}
}
}
你看,一次查询就把所有指标都拿到了。前端直接渲染,后端不用写任何计算逻辑。这就是ES聚合的魅力所在。
text类型字段上做cardinality聚合。结果ES报错说字段需要开启fielddata。后来我改成keyword类型就没事了。所以记住:聚合操作尽量用keyword或数值类型,别用text。
好了,这一章的内容就到这里。Metric聚合是ES聚合查询的基础,掌握了这些,后面学Bucket聚合和Pipeline聚合就会轻松很多。下一章我们聊聊Bucket聚合,到时候你会看到怎么把数据分组、分桶,那才是真正好玩的开始。