一、简介
前面一篇文章已经详细介绍了查询相关的 API,但是当时并没有总结关于Aggregation 聚合查询这一方面的内容,本篇文章单独对聚合查询做一个总结。
聚合查询提供了功能可以分组并统计你的数据。理解聚合最简单的方式就是可以把它粗略的看做 SQL 的 GROUP BY 操作和 SQL 的聚合函数。
ElasticSearch 中常用的聚合有两种:
metric(度量)聚合:度量类型聚合主要针对的 number 类型的数据,需要 ES 做比较多的计算工作,类似于关系型数据库的组函数操作;
bucketing(桶)聚合:划分不同的“桶”,将数据分配到不同的“桶”里。非常类似 sql 中的 group 语句的含义,类似于关系型数据库的分组操作;
关于聚合查询的官方文档地址(es7.6 版本):https://www.elastic.co/guide/en/elasticsearch/reference/7.6/search-aggregations.html#search-aggregations
ES 中的聚合 API 如下:
"aggregations" : { // 表示聚合操作,可以使用 aggs 替代
"<aggregation_name>" : { // 聚合名,可以是任意的字符串。用做响应的 key,便于快速取得正确的响应数据。
"<aggregation_type>" : { // 聚合类别,就是各种类型的聚合,如 min 等
<aggregation_body> // 聚合体,不同的聚合有不同的 body
}
[,"meta" : { [<meta_data_body>] } ]?
[,"aggregations" : { [<sub_aggregation>]+ } ]? // 嵌套的子聚合,可以有 0 或多个
}
[,"<aggregation_name_2>" : { ... } ]* // 另外的聚合,可以有 0 或多个
}
二、数据准备
为了后面演示聚合查询,我们首先需要准备一些数据,批量插入测试数据:
# 批量插入测试数据
POST /user/info/_bulk
{"index":{"_id":1}}
{"name":"zs","realname":"张三","age":10,"birthday":"2018-12-27","salary":1000.0,"address":"广州市天河区科韵路 50 号"}
{"index":{"_id":2}}
{"name":"ls","realname":"李四","age":20,"birthday":"2017-10-20","salary":2000.0,"address":"广州市天河区珠江新城"}
{"index":{"_id":3}}
{"name":"ww","realname":"王五","age":30,"birthday":"2016-03-15","salary":3000.0,"address":"广州市天河区广州塔"}
{"index":{"_id":4}}
{"name":"zl","realname":"赵六","age":40,"birthday":"2003-04-19","salary":4000.0,"address":"广州市海珠区"}
{"index":{"_id":5}}
{"name":"tq","realname":"田七","age":50,"birthday":"2001-08-11","salary":5000.0,"address":"广州市天河区网易大厦"}
插入完成后,查看索引数据:
可见,数据成功插入,下面我们分别对度量聚合、桶聚合进行详细的介绍。
三、度量(metric)聚合
Avg Aggregation
https://www.elastic.co/guide/en/elasticsearch/reference/7.6/search-aggregations-metrics-avg-aggregation.html
平均值聚合查询,用于计算从聚合的文档中提取的数值的平均值,这些值可以从文档中的特定数值字段中提取,也可以由提供的脚本生成。
假设数据由代表用户工资的文件组成,我们可以将他们的平均工资为:
POST /user/info/_search?size=0
{
"aggs": {
"avg_salary": {
"avg": {
"field": "salary"
}
}
}
}
上述聚合计算所有文档的平均级别。聚合类型为 avg,字段设置定义计算平均值的文档的数字字段。以上将返回以下内容:
.............
"aggregations" : {
"avg_salary" : {
"value" : 3000.0
}
}
可以看到,平均工资查询出来为 3000 元。
聚合的名称(上面的 avg_salary)还充当键,通过它可以从返回的响应中检索聚合结果。
Max Aggregation
https://www.elastic.co/guide/en/elasticsearch/reference/7.6/search-aggregations-metrics-max-aggregation.html
最大值查询,跟踪并返回从聚合文档中提取的数值的最大值。这些值可以从文档中的特定数值字段中提取,也可以由提供的脚本生成。
如:查询员工的最高工资
POST /user/info/_search?size=0
{
"aggs": {
"max_salary": {
"max": {
"field": "salary"
}
}
}
}
以上将返回以下内容:
..........
"aggregations" : {
"max_salary" : {
"value" : 5000.0
}
}
可以看到,成功查询出最高工资为 5000 元。
Min Aggregation
https://www.elastic.co/guide/en/elasticsearch/reference/7.6/search-aggregations-metrics-min-aggregation.html
最小值查询,跟踪并返回从聚合文档中提取的数值中的最小值。
如:查询员工最低工资
POST /user/info/_search?size=0
{
"aggs": {
"min_salary": {
"min": {
"field": "salary"
}
}
}
}
以上查询返回如下结果:
........
"aggregations" : {
"min_salary" : {
"value" : 1000.0
}
}
可以看到,成功查询到最低工资 1000 元。
Sum Aggregation
https://www.elastic.co/guide/en/elasticsearch/reference/7.6/search-aggregations-metrics-sum-aggregation.html
求和聚合查询,对从聚合的文档中提取的数值进行汇总。
如,统计所有员工的总工资
POST /user/info/_search?size=0
{
"aggs": {
"sum_salary": {
"sum": {
"field": "salary"
}
}
}
}
以上查询返回如下结果:
......
"aggregations" : {
"sum_salary" : {
"value" : 15000.0
}
}
可以看到,成功查询总工资为 15000 元。
Stats Aggregation
https://www.elastic.co/guide/en/elasticsearch/reference/7.6/search-aggregations-metrics-stats-aggregation.html
统计查询,一次性统计出某个字段上的常用统计值。返回的统计信息包括:最小值,最大值,总和,计数和平均。
POST /user/info/_search?size=0
{
"aggs": {
"price_stats": {
"stats": {
"field": "salary"
}
}
}
}
以上查询返回如下结果:
......
"aggregations" : {
"price_stats" : {
"count" : 5,
"min" : 1000.0,
"max" : 5000.0,
"avg" : 3000.0,
"sum" : 15000.0
}
}
可以看到,stats 聚合查询同时返回了我们前面介绍了一些 max、min、sum、avg 等信息。
Value Count Aggregation
https://www.elastic.co/guide/en/elasticsearch/reference/7.6/search-aggregations-metrics-valuecount-aggregation.html
统计某字段有值的文档数。
POST /user/info/_search?size=0
{
"aggs": {
"price_value_count": {
"value_count": {
"field": "salary"
}
}
}
}
如上查询下面结果:
......
"aggregations" : {
"price_value_count" : {
"value" : 5
}
}
还有一些其他的聚合查询,如:
Cardinality Aggregation :值去重计数
Extended Stats Aggregation:高级统计,比 stats 多 4 个统计结果: 平方和、方差、标准差、平均值加/减两个标准差的区间。
Geo Bounds Aggregation:求文档集中的地理位置坐标点的范围
Geo Centroid Aggregation:求地理位置中心点坐标值
Percentiles Aggregation:占比百分位对应的值统计
Percentile Ranks Aggregation:统计值小于等于指定值的文档占比
感兴趣的小伙伴可以跟着官网示例,自己手动去练练手,试一下。
三、bucketing(桶)聚合
Range Aggregation
https://www.elastic.co/guide/en/elasticsearch/reference/7.6/search-aggregations-bucket-range-aggregation.html
基于多桶值源的聚合,使用户能够定义一组范围——每个范围表示一个桶。在聚合过程中,将根据每个 bucket 范围和相关/匹配文档的“bucket”检查从每个文档提取的值。
注意,此聚合包含每个范围的 from 值并排除 to 值。
自定义区间范围的聚合,我们可以自己手动地划分区间,ES 会根据划分出来的区间将数据分配不同的区间上去。
如: 统计 0-20 岁,20-40 岁,40~60 岁各个区间段的用户人数
POST /user/info/_search
{
"aggs": {
"age_ranges_count": {
"range": {
"field": "age",
"ranges": [
{
"from": 0,
"to": 20
},
{
"from": 20,
"to": 40
},
{
"from": 40,
"to": 60
}
]
}
}
}
}
以上查询返回下面结果:
...........
"aggregations" : {
"age_ranges_count" : {
"buckets" : [
{
"key" : "0.0-20.0",
"from" : 0.0,
"to" : 20.0,
"doc_count" : 1
},
{
"key" : "20.0-40.0",
"from" : 20.0,
"to" : 40.0,
"doc_count" : 2
},
{
"key" : "40.0-60.0",
"from" : 40.0,
"to" : 60.0,
"doc_count" : 2
}
]
}
}
可以看到,成功统计出各个我们指定的区间段的用户人数。如果第一个区间开始值和最后一个区间结束值不想指定的话,可以不用写 from 和 to,如下:
GET /_search
{
"aggs" : {
"price_ranges" : {
"range" : {
"field" : "price",
"ranges" : [
{ "to" : 100.0 },
{ "from" : 100.0, "to" : 200.0 },
{ "from" : 200.0 }
]
}
}
}
}
响应结果就类似下面:*-100.0、200.0-*,使用*代表某个区间值。
{
...
"aggregations": {
"price_ranges" : {
"buckets": [
{
"key": "*-100.0",
"to": 100.0,
"doc_count": 2
},
{
"key": "100.0-200.0",
"from": 100.0,
"to": 200.0,
"doc_count": 2
},
{
"key": "200.0-*",
"from": 200.0,
"doc_count": 3
}
]
}
}
}
Terms Aggregation
https://www.elastic.co/guide/en/elasticsearch/reference/7.6/search-aggregations-bucket-terms-aggregation.html
自定义分组依据 Term,对分组后的数据进行统计。
如:根据年龄分组,统计相同年龄的用户
POST /user/info/_search
{
"aggs": {
"age_count": {
"terms": {
"field": "age",
"size": 3
}
}
}
}
响应结果如下:
.....
"aggregations" : {
"age_count" : {
"doc_count_error_upper_bound" : 0,
"sum_other_doc_count" : 2,
"buckets" : [
{
"key" : 10,
"doc_count" : 1
},
{
"key" : 20,
"doc_count" : 1
},
{
"key" : 30,
"doc_count" : 1
}
]
}
}
Date Range Aggregation
https://www.elastic.co/guide/en/elasticsearch/reference/7.6/search-aggregations-bucket-daterange-aggregation.html
专用于日期值的范围聚合,专门针对 date 类型的字段。此聚合与 Range Aggregation 常规范围聚合的主要区别在于,可以用日期数学表达式表示 from 和 to 值,而且还可以指定返回 from 和 to 响应字段的日期格式。注意,此聚合包含每个范围的 from 值并排除 to 值。
now+10y:表示从现在开始的第 10 年。
now+10M:表示从现在开始的第 10 个月。
1990-01-10||+20y:表示从 1990-01-01 开始后的第 20 年,即 2010-01-01。
now/y:表示在年位上做舍入运算。
如: 统计生日在 2017 年、2018 年、2019 年的用户
POST /user/info/_search
{
"aggs": {
"birthday_count": {
"date_range": {
"field": "birthday",
"format": "yyyy-MM-dd",
"ranges": [
{
"from": "now/y-1y",
"to": "now/y"
},
{
"from": "now/y-2y",
"to": "now/y-1y"
},
{
"from": "now/y-3y",
"to": "now/y-2y"
}
]
}
}
}
}
其中:
now/y:当前年的 1 月 1 日
now:当前时间
now/y-1y:当前年上一年的 1 月 1 日
响应结果:
......
"aggregations" : {
"birthday_count" : {
"buckets" : [
{
"key" : "2017-01-01-2018-01-01",
"from" : 1.4832288E12,
"from_as_string" : "2017-01-01",
"to" : 1.5147648E12,
"to_as_string" : "2018-01-01",
"doc_count" : 1
},
{
"key" : "2018-01-01-2019-01-01",
"from" : 1.5147648E12,
"from_as_string" : "2018-01-01",
"to" : 1.5463008E12,
"to_as_string" : "2019-01-01",
"doc_count" : 1
},
{
"key" : "2019-01-01-2020-01-01",
"from" : 1.5463008E12,
"from_as_string" : "2019-01-01",
"to" : 1.5778368E12,
"to_as_string" : "2020-01-01",
"doc_count" : 0
}
]
}
}
Histogram Aggregation
https://www.elastic.co/guide/en/elasticsearch/reference/7.6/search-aggregations-bucket-histogram-aggregation.html
直方图聚合,它将某个 number 类型字段等分成 n 份,统计落在每一个区间内的记录数。它与前面介绍的 Range 聚合非常像,只不过 Range 可以任意划分区间,而 Histogram 做等间距划分。既然是等间距划分,那么参数里面必然有距离参数,就是 interval 参数。
如:根据年龄间隔(20 岁)统计各个年龄段的员工总人数
POST /user/info/_search
{
"aggs": {
"age_histogram_count": {
"histogram": {
"field": "age",
"interval": 20
}
}
}
}
响应结果如下:
......
"aggregations" : {
"age_histogram_count" : {
"buckets" : [
{
"key" : 0.0,
"doc_count" : 1
},
{
"key" : 20.0,
"doc_count" : 2
},
{
"key" : 40.0,
"doc_count" : 2
}
]
}
}
Date histogram aggregation
https://www.elastic.co/guide/en/elasticsearch/reference/7.6/search-aggregations-bucket-datehistogram-aggregation.html
日期直方图聚合,专门对时间类型的字段做直方图聚合。这种需求是比较常用见得的,我们在统计时,通常就会按照固定的时间断(1 个月或 1 年等)来做统计。
如:按年统计用户生日的总人数
POST /user/info/_search
{
"aggs": {
"birthday_data_histogram_count": {
"date_histogram": {
"field": "birthday",
"interval": "year",
"format": "yyyy-MM-dd"
}
}
}
}
响应结果如下:
......
"aggregations" : {
"birthday_data_histogram_count" : {
"buckets" : [
{
"key_as_string" : "2001-01-01",
"key" : 978307200000,
"doc_count" : 1
},
{
"key_as_string" : "2002-01-01",
"key" : 1009843200000,
"doc_count" : 0
},
{
"key_as_string" : "2003-01-01",
"key" : 1041379200000,
"doc_count" : 1
},
{
"key_as_string" : "2004-01-01",
"key" : 1072915200000,
"doc_count" : 0
},
{
"key_as_string" : "2005-01-01",
"key" : 1104537600000,
"doc_count" : 0
},
{
"key_as_string" : "2006-01-01",
"key" : 1136073600000,
"doc_count" : 0
},
{
"key_as_string" : "2007-01-01",
"key" : 1167609600000,
"doc_count" : 0
},
{
"key_as_string" : "2008-01-01",
"key" : 1199145600000,
"doc_count" : 0
},
{
"key_as_string" : "2009-01-01",
"key" : 1230768000000,
"doc_count" : 0
},
{
"key_as_string" : "2010-01-01",
"key" : 1262304000000,
"doc_count" : 0
},
{
"key_as_string" : "2011-01-01",
"key" : 1293840000000,
"doc_count" : 0
},
{
"key_as_string" : "2012-01-01",
"key" : 1325376000000,
"doc_count" : 0
},
{
"key_as_string" : "2013-01-01",
"key" : 1356998400000,
"doc_count" : 0
},
{
"key_as_string" : "2014-01-01",
"key" : 1388534400000,
"doc_count" : 0
},
{
"key_as_string" : "2015-01-01",
"key" : 1420070400000,
"doc_count" : 0
},
{
"key_as_string" : "2016-01-01",
"key" : 1451606400000,
"doc_count" : 1
},
{
"key_as_string" : "2017-01-01",
"key" : 1483228800000,
"doc_count" : 1
},
{
"key_as_string" : "2018-01-01",
"key" : 1514764800000,
"doc_count" : 1
}
]
}
}
除了上述介绍的几种桶聚合查询,elasticsearch 官网还提供了很多其他各种各样的聚合查询,有兴趣的小伙伴们可以前往官网进行学习。
四、聚合查询嵌套使用
聚合操作是可以嵌套使用的。通过嵌套,可以使得 metric 类型的聚合操作作用在每一 bucket 上。我们可以使用 ES 的嵌套聚合操作来完成稍微复杂一点的统计功能。
如:统计每年中用户的最高工资
POST /user/info/_search
{
"aggs": {
"birthday_data_histogram_count": {
"date_histogram": {
"field": "birthday",
"interval": "year",
"format": "yyyy-MM-dd"
},
"aggs": {
"max_salary": {
"max": {
"field": "salary"
}
}
}
}
}
}
响应结果:
.....
"aggregations" : {
"birthday_data_histogram_count" : {
"buckets" : [
{
"key_as_string" : "2001-01-01",
"key" : 978307200000,
"doc_count" : 1,
"max_salary" : {
"value" : 5000.0
}
},
{
"key_as_string" : "2002-01-01",
"key" : 1009843200000,
"doc_count" : 0,
"max_salary" : {
"value" : null
}
},
{
"key_as_string" : "2003-01-01",
"key" : 1041379200000,
"doc_count" : 1,
"max_salary" : {
"value" : 4000.0
}
},
{
"key_as_string" : "2004-01-01",
"key" : 1072915200000,
"doc_count" : 0,
"max_salary" : {
"value" : null
}
},
{
"key_as_string" : "2005-01-01",
"key" : 1104537600000,
"doc_count" : 0,
"max_salary" : {
"value" : null
}
},
{
"key_as_string" : "2006-01-01",
"key" : 1136073600000,
"doc_count" : 0,
"max_salary" : {
"value" : null
}
},
{
"key_as_string" : "2007-01-01",
"key" : 1167609600000,
"doc_count" : 0,
"max_salary" : {
"value" : null
}
},
{
"key_as_string" : "2008-01-01",
"key" : 1199145600000,
"doc_count" : 0,
"max_salary" : {
"value" : null
}
},
{
"key_as_string" : "2009-01-01",
"key" : 1230768000000,
"doc_count" : 0,
"max_salary" : {
"value" : null
}
},
{
"key_as_string" : "2010-01-01",
"key" : 1262304000000,
"doc_count" : 0,
"max_salary" : {
"value" : null
}
},
{
"key_as_string" : "2011-01-01",
"key" : 1293840000000,
"doc_count" : 0,
"max_salary" : {
"value" : null
}
},
{
"key_as_string" : "2012-01-01",
"key" : 1325376000000,
"doc_count" : 0,
"max_salary" : {
"value" : null
}
},
{
"key_as_string" : "2013-01-01",
"key" : 1356998400000,
"doc_count" : 0,
"max_salary" : {
"value" : null
}
},
{
"key_as_string" : "2014-01-01",
"key" : 1388534400000,
"doc_count" : 0,
"max_salary" : {
"value" : null
}
},
{
"key_as_string" : "2015-01-01",
"key" : 1420070400000,
"doc_count" : 0,
"max_salary" : {
"value" : null
}
},
{
"key_as_string" : "2016-01-01",
"key" : 1451606400000,
"doc_count" : 1,
"max_salary" : {
"value" : 3000.0
}
},
{
"key_as_string" : "2017-01-01",
"key" : 1483228800000,
"doc_count" : 1,
"max_salary" : {
"value" : 2000.0
}
},
{
"key_as_string" : "2018-01-01",
"key" : 1514764800000,
"doc_count" : 1,
"max_salary" : {
"value" : 1000.0
}
}
]
}
}
可以看到,先通过 date_histogram 按照年分组,然后再通过嵌套 max 聚合查询统计出每年最高工资是多少。
如:求每个年龄区间段的工资总和
POST /user/info/_search
{
"aggs": {
"age_ranges_count": {
"range": {
"field": "age",
"ranges": [
{
"from": 0,
"to": 20
},
{
"from": 20,
"to": 40
},
{
"from": 40,
"to": 60
}
]
},
"aggs": {
"sum_salary": {
"sum": {
"field": "salary"
}
}
}
}
}
}
响应结果:
......
"aggregations" : {
"age_ranges_count" : {
"buckets" : [
{
"key" : "0.0-20.0",
"from" : 0.0,
"to" : 20.0,
"doc_count" : 1,
"sum_salary" : {
"value" : 1000.0
}
},
{
"key" : "20.0-40.0",
"from" : 20.0,
"to" : 40.0,
"doc_count" : 2,
"sum_salary" : {
"value" : 5000.0
}
},
{
"key" : "40.0-60.0",
"from" : 40.0,
"to" : 60.0,
"doc_count" : 2,
"sum_salary" : {
"value" : 9000.0
}
}
]
}
}
五、总结
本篇文章主要总结了 elasticsearch 提供的聚合查询,聚合查询类似于我们关系型数据库中的 group by 分组统计信息,这种需求很常见。文章通过详细的示例分别介绍了常见的几种度量聚合、桶聚合查询,在实际工作中,需要结合具体的业务查询需求灵活使用不同的聚合统计函数,更多详细使用方法可参见官网。希望对小伙伴们的学习有所帮助,由于笔者水平有限,文中可能存在有不对之处,还望指正,相互学习,一起进步!
参考文档:
https://www.elastic.co/guide/en/elasticsearch/reference/7.6/search-aggregations.html
https://www.cnblogs.com/wangzhuxing/p/9581947.html#_label1_2
来自:https://blog.csdn.net/Weixiaohuai/article/details/109003560