一,ES统计分析概念
ES中的聚合查询,类似SQL的SUM/AVG/COUNT/GROUP BY分组查询,主要用于统计分析场景。
下面先介绍ES聚合查询的核心流程和核心概念。
1. ES聚合查询流程
ES聚合查询类似SQL的GROUP by,一般统计分析主要分为两个步骤:
分组
组内聚合
对查询的数据首先进行一轮分组,可以设置分组条件,例如:新生入学,把所有的学生按专业分班,这个分班的过程就是对学生进行了分组。
组内聚合,就是对组内的数据进行统计,例如:计算总数、求平均值等等,接上面的例子,学生都按专业分班了,那么就可以统计每个班的学生总数, 这个统计每个班学生总数的计算,就是组内聚合计算。
提示:分组类似SQL的group by语句设定的条件,组内聚合,就是在select编写的avg、sum、count统计函数;熟悉SQL语句都知道sum、count这些统计函数不一定要跟group by语句配合使用,单独使用统计函数等同于将所有数据分成一个组,直接对所有数据进行统计。
2. 核心概念
通过上面的聚合查询流程,下面是ES聚合的核心概念就很容易理解了
2.1. 桶
满足特定条件的文档的集合,叫做桶。
桶的就是一组数据的集合,对数据分组后,得到一组组的数据,就是一个个的桶。
提示:桶等同于组,分桶和分组是一个意思,ES使用桶代表一组相同特征的数据。
ES中桶聚合,指的就是先对数据进行分组,ES支持多种分组条件,例如:支持类似SQL的group by根据字段分组,当然ES比SQL更强大,支持更多的分组条件,以满足各种统计需求。
2.2. 指标
指标指的是对文档进行统计计算方式,又叫指标聚合。
桶内聚合,说的就是先对数据进行分组(分桶),然后对每一个桶内的数据进行指标聚合。
说白了就是,前面将数据经过一轮桶聚合,把数据分成一个个的桶之后,我们根据上面计算指标对桶内的数据进行统计。
常用的指标有:SUM、COUNT、MAX等统计函数。
借助SQL的统计语句理解桶和指标:
SELECTCOUNT(*)FROMorderGROUPBY shop_id
说明:
COUNT(*) 相当于指标, 也叫统计指标。
GROUP BY shop_id 相当于分桶的条件,也可以叫分组条件,相同shop_id的数据都分到一个桶内。
这条SQL语句的作用就是统计每一个店铺的订单数,所以SQL统计的第一步是根据group by shop_id这个条件,把shop_id(店铺ID)相同的数据分到一个组(桶)里面,然后每一组数据使用count(*)统计函数(指标)计算总数,最终得到每一个店铺的订单总数,ES也是类似的过程。
3.ES聚合查询语法
大家可以先大致了解下ES聚合查询的基本语法结构
{
"aggregations" : {
"<aggregation_name>" : {
"<aggregation_type>" : {
<aggregation_body>
}
[,"aggregations" : { [<sub_aggregation>]+ } ]? // 嵌套聚合查询,支持多层嵌套
}
[,"<aggregation_name_2>" : { ... } ]* // 多个聚合查询,每个聚合查询取不同的名字
}
}
说明:
aggregations - 代表聚合查询语句,可以简写为aggs
<aggregation_name> - 代表一个聚合计算的名字,可以随意命名,因为ES支持一次进行多次统计分析查询,后面需要通过这个名字在查询结果中找到我们想要的计算结果。
<aggregation_type> - 聚合类型,代表我们想要怎么统计数据,主要有两大类聚合类型,桶聚合和指标聚合,这两类聚合又包括多种聚合类型,例如:指标聚合:sum、avg, 桶聚合:terms、Date histogram等等。
<aggregation_body> - 聚合类型的参数,选择不同的聚合类型,有不同的参数。
aggregation_name_2 - 代表其他聚合计算的名字,意思就是可以一次进行多种类型的统计。
下面看个简单的聚合查询的例子:
假设存在一个order索引,存储了每一笔汽车销售订单,里面包含了汽车颜色字段color.
GET /order/_search
{
"size" : 0, // 设置size=0的意思就是,仅返回聚合查询结果,不返回普通query查询结果。
"aggs" : { // 聚合查询语句的简写
"popular_colors" : { // 给聚合查询取个名字,叫popular_colors
"terms" : { // 聚合类型为,terms,terms是桶聚合的一种,类似SQL的group by的作用,根据字段分组,相同字段值的文档分为一组。
"field" : "color" // terms聚合类型的参数,这里需要设置分组的字段为color,根据color分组
}
}
}
}
上面使用了terms桶聚合,而且没有明确指定指标聚合函数,默认使用的是Value Count聚合指标统计文档总数, 整个统计的意思是统计每一种汽车颜色的销量。
等价SQL如下:
selectcount(color)fromordergroupby color
查询结果如下
{..."hits":{// 因为size=0,所以query查询结果为空"hits":[]},"aggregations":{// 聚合查询结果"popular_colors":{// 这个就是popular_colors聚合查询的结果,这就是为什么需要给聚合查询取个名字的原因,如果有多个聚合查询,可以通过名字查找结果"buckets":[// 因为是桶聚合,所以看到返回一个buckets数组,代表分组的统计情况,下面可以看到每一种颜色的销量情况{"key":"red","doc_count":4// 红色的汽车销量为4},{"key":"blue","doc_count":2},{"key":"green","doc_count":2}]}}}
二,指标聚合
ES指标聚合,就是类似SQL的统计函数,指标聚合可以单独使用,也可以跟桶聚合一起使用。
常用的统计函数如下:
Value Count - 类似sql的count函数,统计总数
Cardinality - 类似SQL的count(DISTINCT 字段), 统计不重复的数据总数
Avg - 求平均值
Sum - 求和
Max - 求最大值
Min - 求最小值
下面分别介绍Elasticsearch常用统计函数的用法。
1. Value Count
值聚合,主要用于统计文档总数,类似SQL的count函数。
例子:
GET /sales/_search?size=0
{
"aggs": {
"types_count": { // 聚合查询的名字,随便取个名字
"value_count": { // 聚合类型为:value_count
"field": "type" // 计算type这个字段值的总数
}
}
}
}
等价SQL:
select count(type) from sales
返回结果:
{
...
"aggregations": {
"types_count": { // 聚合查询的名字
"value": 7 // 统计结果
}
}
}
2.Cardinality
基数聚合,也是用于统计文档的总数,跟Value Count的区别是,基数聚合会去重,不会统计重复的值,类似SQL的count(DISTINCT 字段)用法。
例子:
POST /sales/_search?size=0
{
"aggs" : {
"type_count" : { // 聚合查询的名字,随便取一个
"cardinality" : { // 聚合查询类型为:cardinality
"field" : "type" // 根据type这个字段统计文档总数
}
}
}
}
等价SQL:
selectcount(DISTINCTtype)from sales
返回结果:
{..."aggregations":{"type_count":{// 聚合查询的名字"value":3// 统计结果}}}
提示:前面提到基数聚合的作用等价于SQL的count(DISTINCT 字段)的用法,其实不太准确,因为SQL的count统计结果是精确统计不会丢失精度,但是ES的cardinality基数聚合统计的总数是一个近似值,会有一定的误差,这么做的目的是为了性能,因为在海量的数据中精确统计总数是非常消耗性能的,但是很多业务场景不需要精确的结果,只要近似值,例如:统计网站一天的访问量,有点误差没关系。
3.Avg
求平均值
例子:
POST/exams/_search?size=0{"aggs":{"avg_grade":{// 聚合查询名字,随便取一个名字"avg":{// 聚合查询类型为: avg"field":"grade"// 统计grade字段值的平均值}}}}
返回结果:
{..."aggregations":{"avg_grade":{// 聚合查询名字"value":75.0// 统计结果}}}
4.Sum
求和计算
例子:
POST/sales/_search?size=0{"aggs":{"hat_prices":{// 聚合查询名字,随便取一个名字"sum":{// 聚合类型为:sum"field":"price"// 计算price字段值的总和}}}}
返回结果:
{
...
"aggregations": {
"hat_prices": { // 聚合查询名字
"value": 450.0 // 统计结果
}
}
}
5.Max
求最大值
例子:
POST /sales/_search?size=0
{
"aggs": {
"max_price": { // 聚合查询名字,随便取一个名字
"max": { // 聚合类型为:max
"field": "price" // 求price字段的最大值
}
}
}
}
返回结果:
{
...
"aggregations": {
"max_price": { // 聚合查询名字
"value": 200.0 // 最大值
}
}
}
6.Min
求最小值
例子:
POST /sales/_search?size=0
{
"aggs": {
"min_price": { // 聚合查询名字,随便取一个
"min": { // 聚合类型为: min
"field": "price" // 求price字段值的最小值
}
}
}
}
返回:
{
...
"aggregations": {
"min_price": { // 聚合查询名字
"value": 10.0 // 最小值
}
}
}
7.综合例子
前面的例子,仅仅介绍聚合指标单独使用的情况,实际应用中经常先通过query查询,搜索索引中的数据,然后对query查询的结果进行统计分析。
例子:
GET/sales/_search
{"size":0,// size = 0,代表不想返回query查询结果,只要统计结果"query":{// 设置query查询条件,后面的aggs统计,仅对query查询结果进行统计"constant_score":{"filter":{"match":{"type":"hat"}}}},"aggs":{// 统计query查询结果, 默认情况如果不写query语句,则代表统计所有数据"hat_prices":{// 聚合查询名字,计算price总和"sum":{"field":"price"}},"min_price":{// 聚合查询名字,计算price最小值"min":{"field":"price"}},"max_price":{// 聚合查询名字,计算price最大值"max":{"field":"price"}}}}
返回:
{
...
"aggregations": {
"hat_prices": { // 求和
"value": 450.0
},
"min_price": { // 最小值
"value": 10.0
},
"max_price": { // 最大值
"value": 200.0
}
}
}
三,分组聚合查询
Elasticsearch桶聚合,目的就是数据分组,先将数据按指定的条件分成多个组,然后对每一个组进行统计。 组的概念跟桶是等同的,在ES中统一使用桶(bucket)这个术语。
ES桶聚合的作用跟SQL的group by的作用是一样的,区别是ES支持更加强大的数据分组能力,SQL只能根据字段的唯一值进行分组,分组的数量跟字段的唯一值的数量相等,例如: group by 店铺id, 去掉重复的店铺ID后,有多少个店铺就有多少个分组。
ES常用的桶聚合如下:
Terms聚合 - 类似SQL的group by,根据字段唯一值分组
Histogram聚合 - 根据数值间隔分组,例如: 价格按100间隔分组,0、100、200、300等等
Date histogram聚合 - 根据时间间隔分组,例如:按月、按天、按小时分组
Range聚合 - 按数值范围分组,例如: 0-150一组,150-200一组,200-500一组。
提示:桶聚合一般不单独使用,都是配合指标聚合一起使用,对数据分组之后肯定要统计桶内数据,在ES中如果没有明确指定指标聚合,默认使用Value Count指标聚合,统计桶内文档总数。
1.Terms聚合
terms聚合的作用跟SQL中group by作用一样,都是根据字段唯一值对数据进行分组(分桶),字段值相等的文档都分到同一个桶内。
例子:
GET /order/_search?size=0
{
"aggs": {
"shop": { // 聚合查询的名字,随便取个名字
"terms": { // 聚合类型为: terms
"field": "shop_id" // 根据shop_id字段值,分桶
}
}
}
}
等价SQL:
select shop_id, count(*) from order group by shop_id
返回结果:
{
...
"aggregations" : {
"shop" : { // 聚合查询名字
"buckets" : [ // 桶聚合结果,下面返回各个桶的聚合结果
{
"key" : "1", // key分桶的标识,在terms聚合中,代表的就是分桶的字段值
"doc_count" : 6 // 默认的指标聚合是统计桶内文档总数
},
{
"key" : "5",
"doc_count" : 3
},
{
"key" : "9",
"doc_count" : 2
}
]
}
}
}
2.Histogram聚合
histogram(直方图)聚合,主要根据数值间隔分组,使用histogram聚合分桶统计结果,通常用在绘制条形图报表。
例子:
POST /sales/_search?size=0
{
"aggs" : {
"prices" : { // 聚合查询名字,随便取一个
"histogram" : { // 聚合类型为:histogram
"field" : "price", // 根据price字段分桶
"interval" : 50 // 分桶的间隔为50,意思就是price字段值按50间隔分组
}
}
}
}
返回结果:
{
...
"aggregations": {
"prices" : { // 聚合查询名字
"buckets": [ // 分桶结果
{
"key": 0.0, // 桶的标识,histogram分桶,这里通常是分组的间隔值
"doc_count": 1 // 默认按Value Count指标聚合,统计桶内文档总数
},
{
"key": 50.0,
"doc_count": 1
},
{
"key": 100.0,
"doc_count": 0
},
{
"key": 150.0,
"doc_count": 2
}
]
}
}
}
3.Date histogram聚合
类似histogram聚合,区别是Date histogram可以很好的处理时间类型字段,主要用于根据时间、日期分桶的场景。
例子:
POST /sales/_search?size=0
{
"aggs" : {
"sales_over_time" : { // 聚合查询名字,随便取一个
"date_histogram" : { // 聚合类型为: date_histogram
"field" : "date", // 根据date字段分组
"calendar_interval" : "month", // 分组间隔:month代表每月、支持minute(每分钟)、hour(每小时)、day(每天)、week(每周)、year(每年)
"format" : "yyyy-MM-dd" // 设置返回结果中桶key的时间格式
}
}
}
}
返回结果:
{
...
"aggregations": {
"sales_over_time": { // 聚合查询名字
"buckets": [ // 桶聚合结果
{
"key_as_string": "2015-01-01", // 每个桶key的字符串标识,格式由format指定
"key": 1420070400000, // key的具体字段值
"doc_count": 3 // 默认按Value Count指标聚合,统计桶内文档总数
},
{
"key_as_string": "2015-02-01",
"key": 1422748800000,
"doc_count": 2
},
{
"key_as_string": "2015-03-01",
"key": 1425168000000,
"doc_count": 2
}
]
}
}
}
4.Range聚合
range聚合,按数值范围分桶。
例子:
GET /_search
{
"aggs" : {
"price_ranges" : { // 聚合查询名字,随便取一个
"range" : { // 聚合类型为: range
"field" : "price", // 根据price字段分桶
"ranges" : [ // 范围配置
{ "to" : 100.0 }, // 意思就是 price <= 100的文档归类到一个桶
{ "from" : 100.0, "to" : 200.0 }, // price>100 and price<200的文档归类到一个桶
{ "from" : 200.0 } // price>200的文档归类到一个桶
]
}
}
}
}
返回结果:
{
...
"aggregations": {
"price_ranges" : { // 聚合查询名字
"buckets": [ // 桶聚合结果
{
"key": "*-100.0", // key可以表达分桶的范围
"to": 100.0, // 结束值
"doc_count": 2 // 默认按Value Count指标聚合,统计桶内文档总数
},
{
"key": "100.0-200.0",
"from": 100.0, // 起始值
"to": 200.0, // 结束值
"doc_count": 2
},
{
"key": "200.0-*",
"from": 200.0,
"doc_count": 3
}
]
}
}
}
大家仔细观察的话,发现range分桶,默认key的值不太友好,尤其开发的时候,不知道key长什么样子,处理起来比较麻烦,我们可以为每一个分桶指定一个有意义的名字。
例子:
GET /_search
{
"aggs" : {
"price_ranges" : {
"range" : {
"field" : "price",
"keyed" : true,
"ranges" : [
// 通过key参数,配置每一个分桶的名字
{ "key" : "cheap", "to" : 100 },
{ "key" : "average", "from" : 100, "to" : 200 },
{ "key" : "expensive", "from" : 200 }
]
}
}
}
}
5.综合例子
前面的例子,都是单独使用aggs聚合语句,代表直接统计所有的文档,实际应用中,经常需要配合query语句,先搜索目标文档,然后使用aggs聚合语句对搜索结果进行统计分析。
例子:
GET/cars/_search
{"size":0,// size=0代表不需要返回query查询结果,仅仅返回aggs统计结果"query":{// 设置查询语句,先赛选文档"match":{"make":"ford"}},"aggs":{// 然后对query搜索的结果,进行统计"colors":{// 聚合查询名字"terms":{// 聚合类型为:terms 先分桶"field":"color"},"aggs":{// 通过嵌套聚合查询,设置桶内指标聚合条件"avg_price":{// 聚合查询名字"avg":{// 聚合类型为: avg指标聚合"field":"price"// 根据price字段计算平均值}},"sum_price":{// 聚合查询名字"sum":{// 聚合类型为: sum指标聚合"field":"price"// 根据price字段求和}}}}}}
聚合查询支持多层嵌套。
四,聚合后排序
类似terms、histogram、date_histogram这类桶聚合都会动态生成多个桶,如果生成的桶特别多,我们如何确定这些桶的排序顺序,如何限制返回桶的数量。
1.多桶排序
默认情况,ES会根据doc_count文档总数,降序排序。
ES桶聚合支持两种方式排序:
内置排序
按度量指标排序
1.1. 内置排序
内置排序参数:
_count - 按文档数排序。对 terms 、 histogram 、 date_histogram 有效
_term - 按词项的字符串值的字母顺序排序。只在 terms 内使用
_key - 按每个桶的键值数值排序, 仅对 histogram 和 date_histogram 有效
例子:
GET /cars/_search
{
"size" : 0,
"aggs" : {
"colors" : { // 聚合查询名字,随便取一个
"terms" : { // 聚合类型为: terms
"field" : "color",
"order": { // 设置排序参数
"_count" : "asc" // 根据_count排序,asc升序,desc降序
}
}
}
}
}
1.2. 按度量排序
通常情况下,我们根据桶聚合分桶后,都会对桶内进行多个维度的指标聚合,所以我们也可以根据桶内指标聚合的结果进行排序。
例子:
GET /cars/_search
{
"size" : 0,
"aggs" : {
"colors" : { // 聚合查询名字
"terms" : { // 聚合类型: terms,先分桶
"field" : "color", // 分桶字段为color
"order": { // 设置排序参数
"avg_price" : "asc" // 根据avg_price指标聚合结果,升序排序。
}
},
"aggs": { // 嵌套聚合查询,设置桶内聚合指标
"avg_price": { // 聚合查询名字,前面排序引用的就是这个名字
"avg": {"field": "price"} // 计算price字段平均值
}
}
}
}
}
2.限制返回桶的数量
如果分桶的数量太多,可以通过给桶聚合增加一个size参数限制返回桶的数量。
例子:
GET /_search
{
"aggs" : {
"products" : { // 聚合查询名字
"terms" : { // 聚合类型为: terms
"field" : "product", // 根据product字段分桶
"size" : 5 // 限制最多返回5个桶
}
}
}
}
版权归原作者 书虫罢了 所有, 如有侵权,请联系我们删除。