略微加速

略速 - 互联网笔记

ElasticSearch 的 聚合(Aggregations)

2020-12-16 leiting (3959阅读)

标签 Elasticsearch

Elasticsearch有一个功能叫做 聚合(aggregations) ,它允许你在数据上生成复杂的分析统计。它很像SQL中的 GROUP BY 但是功能更强大。

Aggregations种类分为:

  • Metrics, Metrics 是简单的对过滤出来的数据集进行avg,max等操作,是一个单一的数值。

  • Bucket, Bucket 你则可以理解为将过滤出来的数据集按条件分成多个小数据集,然后Metrics会分别作用在这些小数据集上。

对于最后聚合出来的结果,其实我们还希望能进一步做处理,所以有了Pipline Aggregations,其实就是组合一堆的Aggregations 对已经聚合出来的结果再做处理。

Elasticsearch 从1.0开始支持aggregation,基本上有了普通SQL的聚合能力。从 2.0 开始支持 pipeline aggregation,可以支持类似SQL sub query的嵌套聚合的能力。 

简单聚合的例子

内容来自:

http://es.xiaoleilu.com/010_Intro/35_Tutorial_Aggregations.html

我们找到所有职员中最大的共同点(兴趣爱好)是什么:

GET /megacorp/employee/_search 

  "aggs": { 
    "all_interests": { 
      "terms": { "field": "interests" } 
    } 
  } 
}

暂时先忽略语法只看查询结果:


   ... 
   "hits": { ... }, 
   "aggregations": { 
      "all_interests": { 
         "buckets": [ 
            { 
               "key":       "music", 
               "doc_count": 2 
            }, 
            { 
               "key":       "forestry", 
               "doc_count": 1 
            }, 
            { 
               "key":       "sports", 
               "doc_count": 1 
            } 
         ] 
      } 
   } 
}

我们可以看到两个职员对音乐有兴趣,一个喜欢林学,一个喜欢运动。这些数据并没有被预先计算好,它们是实时的从匹配查询语句的文档中动态计算生成的。

如果我们想知道所有姓"Smith"的人最大的共同点(兴趣爱好),我们只需要增加合适的语句既可: 

GET /megacorp/employee/_search 

  "query": { 
    "match": { 
      "last_name": "smith" 
    } 
  }, 
  "aggs": { 
    "all_interests": { 
      "terms": { 
        "field": "interests" 
      } 
    } 
  } 
}

all_interests聚合已经变成只包含和查询语句相匹配的文档了:

... 
  "all_interests": { 
     "buckets": [ 
        { 
           "key": "music", 
           "doc_count": 2 
        }, 
        { 
           "key": "sports", 
           "doc_count": 1 
        } 
     ] 
  }

聚合也允许分级汇总。例如,让我们统计每种兴趣下职员的平均年龄:

GET /megacorp/employee/_search 

    "aggs" : { 
        "all_interests" : { 
            "terms" : { "field" : "interests" }, 
            "aggs" : { 
                "avg_age" : { 
                    "avg" : { "field" : "age" } 
                } 
            } 
        } 
    } 
}

虽然这次返回的聚合结果有些复杂,但任然很容易理解:

... 
  "all_interests": { 
     "buckets": [ 
        { 
           "key": "music", 
           "doc_count": 2, 
           "avg_age": { 
              "value": 28.5 
           } 
        }, 
        { 
           "key": "forestry", 
           "doc_count": 1, 
           "avg_age": { 
              "value": 35 
           } 
        }, 
        { 
           "key": "sports", 
           "doc_count": 1, 
           "avg_age": { 
              "value": 25 
           } 
        } 
     ] 
  }

该聚合结果比之前的聚合结果要更加丰富。我们依然得到了兴趣以及数量(指具有该兴趣的员工人数)的列表,但是现在每个兴趣额外拥有avg_age字段来显示具有该兴趣员工的平均年龄。

 

Aggregations 一些概念

来自: http://blog.csdn.net/dm_vincent/article/details/42387161

Buckets(桶):

满足某个条件的文档集合。

Metrics(指标):

为某个桶中的文档计算得到的统计信息。

如果是SQL,  则 SELECT COUNT(color)  FROM table GROUP BY color 这个语句中的COUNT(color)就相当于一个指标。GROUP BY color则相当于一个桶。

桶和SQL中的组(Grouping)拥有相似的概念,而指标则与COUNT(),SUM(),MAX()等相似。 

桶(Buckets)

一个桶就是满足特定条件的一个文档集合:

  • 一名员工要么属于男性桶,或者女性桶。

  • 城市Albany属于New York州这个桶。

  • 日期2014-10-28属于十月份这个桶。

随着聚合被执行,每份文档中的值会被计算来决定它们是否匹配了桶的条件。如果匹配成功,那么该文档会被置入该桶中,同时聚合会继续执行。

桶也能够嵌套在其它桶中,能让你完成层次或者条件划分这些需求。比如,Cincinnati可以被放置在Ohio州这个桶中,而整个Ohio州则能够被放置在美国这个桶中。

ES中有很多类型的桶,让你可以将文档通过多种方式进行划分(按小时,按最流行的词条,按年龄区间,按地理位置,以及更多)。但是从根本上,它们都根据相同的原理运作:按照条件对文档进行划分。

指标(Metrics)

桶能够让我们对文档进行有意义的划分,但是最终我们还是需要对每个桶中的文档进行某种指标计算。分桶是达到最终目的的手段:提供了对文档进行划分的方法,从而让你能够计算需要的指标。

多数指标仅仅是简单的数学运算(比如,min,mean,max以及sum),它们使用文档中的值进行计算。在实际应用中,指标能够让你计算例如平均薪资,最高出售价格,或者百分之95的查询延迟。

将两者结合起来

一个聚合就是一些桶和指标的组合。一个聚合可以只有一个桶,或者一个指标,或者每样一个。在桶中甚至可以有多个嵌套的桶。比如,我们可以将文档按照其所属国家进行分桶,然后对每个桶计算其平均薪资(一个指标)。

因为桶是可以嵌套的,我们能够实现一个更加复杂的聚合操作:

  1. 将文档按照国家进行分桶。(桶)

  2. 然后将每个国家的桶再按照性别分桶。(桶)

  3. 然后将每个性别的桶按照年龄区间进行分桶。(桶)

  4. 最后,为每个年龄区间计算平均薪资。(指标)

此时,就能够得到每个<国家,性别,年龄>组合的平均薪资信息了。它可以通过一个请求,一次数据遍历来完成!

 

聚合的进一步演示

根据这个理论,我们再来看看聚合,我们建立一个也许对汽车交易商有所用处的聚合。

内容来自: http://blog.csdn.net/dm_vincent/article/details/42407823


数据

数据是关于汽车交易的:汽车型号,制造商,销售价格,销售时间以及一些其他的相关数据。

POST /cars/transactions/_bulk 
{ "index": {}} 
{ "price" : 10000, "color" : "red", "make" : "honda", "sold" : "2014-10-28" } 
{ "index": {}} 
{ "price" : 20000, "color" : "red", "make" : "honda", "sold" : "2014-11-05" } 
{ "index": {}} 
{ "price" : 30000, "color" : "green", "make" : "ford", "sold" : "2014-05-18" } 
{ "index": {}} 
{ "price" : 15000, "color" : "blue", "make" : "toyota", "sold" : "2014-07-02" } 
{ "index": {}} 
{ "price" : 12000, "color" : "green", "make" : "toyota", "sold" : "2014-08-19" } 
{ "index": {}} 
{ "price" : 20000, "color" : "red", "make" : "honda", "sold" : "2014-11-05" } 
{ "index": {}} 
{ "price" : 80000, "color" : "red", "make" : "bmw", "sold" : "2014-01-01" } 
{ "index": {}} 
{ "price" : 25000, "color" : "blue", "make" : "ford", "sold" : "2014-02-12" }

哪种颜色车好卖

我们这次建立一个聚合:一个汽车交易商也许希望知道哪种颜色的车卖的最好。这可以通过一个简单的聚合完成。使用terms桶:

GET /cars/transactions/_search?search_type=count 

    "aggs" : { 
        "colors" : { 
            "terms" : { 
              "field" : "color" 
            } 
        } 
    } 
}

因为我们并不关心搜索结果,使用的search_type是count,它的速度更快。

聚合工作在顶层的aggs参数下(当然你也可以使用更长的aggregations)。

然后给这个聚合起了一个名字:colors。

最后,我们定义了一个terms类型的桶,它针对color字段。

我们为聚合起一个名字colors。命名规则是有你决定的; 聚合的响应会被该名字标记,因此在应用中你就能够根据名字来得到聚合结果,并对它们进行操作了。

比如,我们定义了一个terms类型的桶。terms桶会动态地为每一个它遇到的不重复的词条创建一个新的桶。因为我们针对的是color字段,那么terms桶会动态地为每种颜色创建一个新桶。

让我们执行该聚合来看看其结果:


... 
   "hits": { 
      "hits": [] 
   }, 
   "aggregations": { 
      "colors": { 
         "buckets": [ 
            { 
               "key": "red", 
               "doc_count": 4 
            }, 
            { 
               "key": "blue", 
               "doc_count": 2 
            }, 
            { 
               "key": "green", 
               "doc_count": 2 
            } 
         ] 
      } 
   } 
}

因为我们使用的search_type为count,所以没有搜索结果被返回。 每个桶中的key对应的是在color字段中找到的不重复的词条。它同时也包含了一个doc_count,用来表示包含了该词条的文档数量。

响应包含了一个桶列表,每个桶都对应着一个不重复的颜色(比如,红色或者绿色)。每个桶也包含了“掉入”该桶中的文档数量。比如,有4辆红色的车。

前面的例子是完全实时(Real-Time)的:如果文档是可搜索的,那么它们就能够被聚合。这意味着你能够将拿到的聚合结果置入到一个图形库中来生成实时的仪表板(Dashboard)。一旦你卖出了一台银色汽车,在图形上关于银色汽车的统计数据就会被动态地更新。

 

添加一个指标(Metric)

从前面的例子中,我们可以知道每个桶中的文档数量。但是,通常我们的应用会需要基于那些文档的更加复杂的指标(Metric)。比如,每个桶中的汽车的平均价格是多少?

为了得到该信息,我们得告诉ES需要为哪些字段计算哪些指标。这需要将指标嵌套到桶中。指标会基于桶中的文档的值来计算相应的统计信息。

让我们添加一个计算平均值的指标: 

GET /cars/transactions/_search?search_type=count 

   "aggs": { 
      "colors": { 
         "terms": { 
            "field": "color" 
         }, 
         "aggs": { 
            "avg_price": { 
               "avg": { 
                  "field": "price" 
               } 
            } 
         } 
      } 
   } 
}

我们添加了一个新的aggs层级来包含该指标。然后给该指标起了一个名字:avg_price。最后定义了该指标作用的字段为price。.

正如你所看到的,我们向前面的例子中添加了一个新的aggs层级。这个新的聚合层级能够让我们将avg指标嵌套在terms桶中。这意味着我们能为每种颜色都计算一个平均值。

同样的,我们需要给指标起一个名(avg_price)来让我们能够在将来得到其值。最后,我们指定了指标本身(avg)以及该指标作用的字段(price):


... 
   "aggregations": { 
      "colors": { 
         "buckets": [ 
            { 
               "key": "red", 
               "doc_count": 4, 
               "avg_price": { 
                  "value": 32500 
               } 
            }, 
            { 
               "key": "blue", 
               "doc_count": 2, 
               "avg_price": { 
                  "value": 20000 
               } 
            }, 
            { 
               "key": "green", 
               "doc_count": 2, 
               "avg_price": { 
                  "value": 21000 
               } 
            } 
         ] 
      } 
   } 
... 

现在,在响应中多了一个avg_price元素。

尽管得到的响应只是稍稍有些变化,但是获得的数据增加的了许多。之前我们只知道有4辆红色汽车。现在我们知道了红色汽车的平均价格是32500刀。这些数据你可以直接插入到报表中。

 

桶中的桶(Buckets inside Buckets)

当你开始使用不同的嵌套模式时,聚合强大的能力才会显现出来。在前面的例子中,我们已经知道了如何将一个指标嵌套进一个桶的,它的功能已经十分强大了。

但是真正激动人心的分析功能来源于嵌套在其它桶中的桶。现在,让我们来看看如何找到每种颜色的汽车的制造商分布信息: 

GET /cars/transactions/_search?search_type=count 

   "aggs": { 
      "colors": { 
         "terms": { 
            "field": "color" 
         }, 
         "aggs": { 
            "avg_price": { 
               "avg": { 
                  "field": "price" 
               } 
            }, 
            "make": { 
                "terms": { 
                    "field": "make" 
                } 
            } 
         } 
      } 
   } 
}

此时发生了一些有意思的事情。首先,你会注意到前面的avg_price指标完全没有变化。一个聚合的每个层级都能够拥有多个指标或者桶。avg_price指标告诉了我们每种汽车颜色的平均价格。为每种颜色创建的桶和指标是各自独立的。

这个性质对你的应用而言是很重要的,因为你经常需要收集一些互相关联却又完全不同的指标。聚合能够让你对数据遍历一次就得到所有需要的信息。

另外一件重要的事情是添加了新聚合make,它是一个terms类型的桶(嵌套在名为colors的terms桶中)。这意味着我们会根据数据集创建不重复的(color, make)组合。

让我们来看看得到的响应(有省略,因为响应太长了):


... 
   "aggregations": { 
      "colors": { 
         "buckets": [ 
            { 
               "key": "red", 
               "doc_count": 4, 
               "make": { 
                  "buckets": [ 
                     { 
                        "key": "honda", 
                        "doc_count": 3 
                     }, 
                     { 
                        "key": "bmw", 
                        "doc_count": 1 
                     } 
                  ] 
               }, 
               "avg_price": { 
                  "value": 32500 
               } 
            }, 
... 

该响应告诉了我们如下信息:

  • 有4辆红色汽车。

  • 红色汽车的平均价格是32500美刀。

  • 红色汽车中的3辆是Honda,1辆是BMW。

 

最后的一个修改(One Final Modification)


在继续讨论新的话题前,为了把问题讲清楚让我们对该例子进行最后一个修改。为每个制造商添加两个指标来计算最低和最高价格:

GET /cars/transactions/_search?search_type=count 

   "aggs": { 
      "colors": { 
         "terms": { 
            "field": "color" 
         }, 
         "aggs": { 
            "avg_price": { "avg": { "field": "price" } 
            }, 
            "make" : { 
                "terms" : { 
                    "field" : "make" 
                }, 
                "aggs" : { 
                    "min_price" : { "min": { "field": "price"} }, 
                    "max_price" : { "max": { "field": "price"} } 
                } 
            } 
         } 
      } 
   } 

我们需要添加另一个aggs层级来进行对min和max的嵌套。

得到的响应如下(仍然有省略):


... 
   "aggregations": { 
      "colors": { 
         "buckets": [ 
            { 
               "key": "red", 
               "doc_count": 4, 
               "make": { 
                  "buckets": [ 
                     { 
                        "key": "honda", 
                        "doc_count": 3, 
                        "min_price": { 
                           "value": 10000 
                        }, 
                        "max_price": { 
                           "value": 20000 
                        } 
                     }, 
                     { 
                        "key": "bmw", 
                        "doc_count": 1, 
                        "min_price": { 
                           "value": 80000 
                        }, 
                        "max_price": { 
                           "value": 80000 
                        } 
                     } 
                  ] 
               }, 
               "avg_price": { 
                  "value": 32500 
               } 
            }, 
... 


... 
   "aggregations": { 
      "colors": { 
         "buckets": [ 
            { 
               "key": "red", 
               "doc_count": 4, 
               "make": { 
                  "buckets": [ 
                     { 
                        "key": "honda", 
                        "doc_count": 3, 
                        "min_price": { 
                           "value": 10000 
                        }, 
                        "max_price": { 
                           "value": 20000 
                        } 
                     }, 
                     { 
                        "key": "bmw", 
                        "doc_count": 1, 
                        "min_price": { 
                           "value": 80000 
                        }, 
                        "max_price": { 
                           "value": 80000 
                        } 
                     } 
                  ] 
               }, 
               "avg_price": { 
                  "value": 32500 
               } 
            }, 
... 

在每个make桶下,多了min和max的指标。

此时,我们可以得到如下信息:

  • 有4辆红色汽车。

  • 红色汽车的平均价格是32500美刀。

  • 红色汽车中的3辆是Honda,1辆是BMW。

  • 红色Honda汽车中,最便宜的价格为10000美刀。

  • 最贵的红色Honda汽车为20000美刀。


参考资料:

ElasticSearch Aggregations 分析 
http://www.jianshu.com/p/56ad2b7e27b7

ElasticSearch Aggregation Bucket 实例分析 
http://www.jianshu.com/p/643a946d6c9a

http://es.xiaoleilu.com/010_Intro/35_Tutorial_Aggregations.html 


原文链接:https://www.cnblogs.com/ghj1976/p/5311183.html


北京半月雨文化科技有限公司.版权所有 京ICP备12026184号-3