在Mysql中,我们可以获取一组数据的 **最大值(Max)、最小值(Min)**。同样我们能够对这组数据进行 **分组(Group)**。那么对于Elasticsearch中
我们也可以实现同样的功能,聚合有关资料官方文档内容较多,这里大概分3篇或者4篇博客写这个有关Elasticsearch聚合。
官方对聚合有四个关键字:
Metric(指标)
、Bucketing(桶)
、Matrix(矩阵)
、Pipeline(管道)
。
一、聚合概念
1. ES聚合分析是什么?
概念
Elasticsearch除全文检索功能外提供的针对Elasticsearch数据做统计分析的功能。它的实时性高,所有的计算结果都是即时返回。
Elasticsearch将聚合分析主要分为如下4类:
Metric(指标): 指标分析类型,如计算最大值、最小值、平均值等等 (对桶内的文档进行聚合分析的操作)
Bucket(桶): 分桶类型,类似SQL中的GROUP BY语法 (满足特定条件的文档的集合)
Pipeline(管道): 管道分析类型,基于上一级的聚合分析结果进行在分析
Matrix(矩阵): 矩阵分析类型(聚合是一种面向数值型的聚合,用于计算一组文档字段中的统计信息)
2.ES聚合分析查询的写法
在查询请求体中以aggregations节点按如下语法定义聚合分析:
"aggregations" : {
"<aggregation_name>" : { <!--聚合的名字 -->
"<aggregation_type>" : { <!--聚合的类型 -->
<aggregation_body> <!--聚合体:对哪些字段进行聚合 -->
}
[,"meta" : { [<meta_data_body>] } ]? <!--元 -->
[,"aggregations" : { [<sub_aggregation>]+ } ]? <!--在聚合里面在定义子聚合 -->
}
[,"<aggregation_name_2>" : { ... } ]* <!--聚合的名字 -->
}
说明
:aggregations 也可简写为 aggs
3、指标(metric)和 桶(bucket)
虽然Elasticsearch有四种聚合方式,但在一般实际开发中,用到的比较多的就是Metric和Bucket。
(1) 桶(bucket)
a、简单来说桶就是满足特定条件的文档的集合。
b、当聚合开始被执行,每个文档里面的值通过计算来决定符合哪个桶的条件,如果匹配到,文档将放入相应的桶并接着开始聚合操作。
c、桶也可以被嵌套在其他桶里面。
(2)指标(metric)
a、桶能让我们划分文档到有意义的集合,但是最终我们需要的是对这些桶内的文档进行一些指标的计算。分桶是一种达到目的地的手段:它提供了一种给文档分组的方法来让
我们可以计算感兴趣的指标。
b、大多数指标是简单的数学运算(如:最小值、平均值、最大值、汇总),这些是通过文档的值来计算的。
二、指标(Metric)详解
官网
: 指标聚合官网文档:Metric
Metric聚合分析分为单值分析和多值分析两类:
#1、单值分析,只输出一个分析结果
min,max,avg,sum,cardinality
#2、多值分析,输出多个分析结果
stats,extended_stats,percentile,percentile_rank,top hits
1、Avg(平均值)
计算从聚合文档中提取的数值的平均值。
POST /exams/_search?size=0 { "aggs" : { "avg_grade" : { "avg" : { "field" : "grade" } } } }
2、Max(最大值)
计算从聚合文档中提取的数值的最大值。
POST /sales/_search?size=0 { "aggs" : { "max_price" : { "max" : { "field" : "price" } } } }
3、Min(最小值)
计算从聚合文档中提取的数值的最小值。
POST /sales/_search?size=0 { "aggs" : { "min_price" : { "min" : { "field" : "price" } } } }
4、Sum(总和)
计算从聚合文档中提取的数值的总和。
POST /sales/_search?size=0 { "query" : { "constant_score" : { "filter" : { "match" : { "type" : "hat" } } } }, "aggs" : { "hat_prices" : { "sum" : { "field" : "price" } } } }
5、 Cardinality(唯一值)
cardinality 求唯一值,即不重复的字段有多少(相当于mysql中的distinct)
POST /sales/_search?size=0 { "aggs" : { "type_count" : { "cardinality" : { "field" : "type" } } } }
6、Stats
stats 统计,请求后会直接显示多种聚合结果
POST /exams/_search?size=0 { "aggs" : { "grades_stats" : { "stats" : { "field" : "grade" } } } }
返回
{ ... "aggregations": { "grades_stats": { "count": 2, "min": 50.0, "max": 100.0, "avg": 75.0, "sum": 150.0 } } }
7、Percentiles
对指定字段的值按从小到大累计每个值对应的文档数的占比,返回指定占比比例对应的值。
1)默认取百分比
默认按照[ 1, 5, 25, 50, 75, 95, 99 ]来统计
GET latency/_search { "size": 0, "aggs" : { "load_time_outlier" : { "percentiles" : { "field" : "load_time" } } } }
返回结果可以理解为:占比为50%的文档的age值 <= 445,或反过来:age<=445的文档数占总命中文档数的50%
{ ... "aggregations": { "load_time_outlier": { "values" : { "1.0": 5.0, "5.0": 25.0, "25.0": 165.0, "50.0": 445.0, "75.0": 725.0, "95.0": 945.0, "99.0": 985.0 } } } }
2)指定分位值
GET latency/_search { "size": 0, "aggs" : { "load_time_outlier" : { "percentiles" : { "field" : "load_time", "percents" : [95, 99, 99.9] } } } }
3) Keyed Response
默认情况下,keyed标志设置为true,它将唯一的字符串键与每个存储桶相关联,并将范围作为哈希而不是数组返回。
GET latency/_search { "size": 0, "aggs": { "load_time_outlier": { "percentiles": { "field": "load_time", "keyed": false } } } }
返回结果
{ ... "aggregations": { "load_time_outlier": { "values": [ { "key": 1.0, "value": 5.0 }, { "key": 5.0, "value": 25.0 }, { "key": 25.0, "value": 165.0 }, { "key": 50.0, "value": 445.0 }, { "key": 75.0, "value": 725.0 }, { "key": 95.0, "value": 945.0 }, { "key": 99.0, "value": 985.0 } ] } } }
8、 Percentile Ranks
上面是通过百分比求文档值,这里通过文档值求百分比。
GET latency/_search { "size": 0, "aggs" : { "load_time_ranks" : { "percentile_ranks" : { "field" : "load_time", "values" : [500, 600] } } } }
返回结果
{ ... "aggregations": { "load_time_ranks": { "values" : { "500.0": 55.1, "600.0": 64.0 } } } }
结果说明
:时间小于500的文档占比为55.1%,时间小于600的文档占比为64%,
9、Top Hits
一般用于分桶后获取该桶内匹配前n的文档列表
POST /sales/_search?size=0 { "aggs": { "top_tags": { "terms": { "field": "type", #根据type进行分组 每组显示前3个文档 "size": 3 }, "aggs": { "top_sales_hits": { "top_hits": { "sort": [ { "date": { "order": "desc" #按照时间进行倒叙排序 } } ], "_source": { "includes": [ "date", "price" ] #只显示文档指定字段 }, "size" : 1 } } } } } }
三、示例
下面会针对上面官方文档的例子进行举例说明。
1、添加测试数据
1)创建索引
DELETE /employees PUT /employees/ { "mappings" : { "properties" : { "age" : { "type" : "integer" }, "gender" : { "type" : "keyword" }, "job" : { "type" : "text", "fields" : { "keyword" : { "type" : "keyword", "ignore_above" : 50 } } }, "name" : { "type" : "keyword" }, "salary" : { "type" : "integer" } } } }
2)添加数据
添加10条数据,每条数据包含:姓名、年龄、工作、性别、薪资
PUT /employees/_bulk { "index" : { "_id" : "1" } } { "name" : "Emma","age":32,"job":"Product Manager","gender":"female","salary":35000 } { "index" : { "_id" : "2" } } { "name" : "Underwood","age":41,"job":"Dev Manager","gender":"male","salary": 50000} { "index" : { "_id" : "3" } } { "name" : "Tran","age":25,"job":"Web Designer","gender":"male","salary":18000 } { "index" : { "_id" : "4" } } { "name" : "Rivera","age":26,"job":"Web Designer","gender":"female","salary": 22000} { "index" : { "_id" : "5" } } { "name" : "Rose","age":25,"job":"QA","gender":"female","salary":18000 } { "index" : { "_id" : "6" } } { "name" : "Lucy","age":31,"job":"QA","gender":"female","salary": 25000} { "index" : { "_id" : "7" } } { "name" : "Byrd","age":27,"job":"QA","gender":"male","salary":20000 } { "index" : { "_id" : "8" } } { "name" : "Foster","age":27,"job":"Java Programmer","gender":"male","salary": 20000} { "index" : { "_id" : "9" } } { "name" : "Gregory","age":32,"job":"Java Programmer","gender":"male","salary":22000 } { "index" : { "_id" : "10" } } { "name" : "Bryant","age":20,"job":"Java Programmer","gender":"male","salary": 9000}
2、求薪资最低值
POST employees/_search { "size": 0, "aggs": { "min_salary": { "min": { "field":"salary" } } } }
返回
3、找到最低、最高和平均工资
POST employees/_search { "size": 0, "aggs": { "max_salary": { "max": { "field": "salary" } }, "min_salary": { "min": { "field": "salary" } }, "avg_salary": { "avg": { "field": "salary" } } } }
4、一个聚合,输出多值
POST employees/_search { "size": 0, "aggs": { "stats_salary": { "stats": { "field":"salary" } } } }
返回
5、求一共有多少工作类型
POST employees/_search { "size": 0, "aggs": { "cardinate": { "cardinality": { "field": "job.keyword" } } } }
返回
注意
我们需要把job的类型为keyword
类型,这样就不会分词,把它当成一个整体。
6、查看中位数的薪资
POST employees/_search { "size": 0, "aggs": { "load_time_outlier": { "percentiles": { "field": "salary", "percents" : [50, 99], "keyed": false } } } }
返回
发现这些工作的中位数是:21000元。
7、取每个工作类型薪资最高的数据
多层嵌套
根据工作类型分桶,然后按照性别分桶,计算每个桶中工资的最高的薪资。
POST employees/_search { "size": 0, "aggs": { "Job_gender_stats": { "terms": { "field": "job.keyword" }, "aggs": { "gender_stats": { "terms": { "field": "gender" }, "aggs": { "salary_stats": { "max": { "field": "salary" } } } } } } } }
返回
参考
1、Elasticsearch核心技术与实战---阮一鸣(eBay Pronto平台技术负责人