🥖ElasticSearch-09-聚合查询之Bucket聚合

除了查询之外,最常用的聚合了,ElasticSearch提供了三种聚合方式: 桶聚合(Bucket Aggregation)指标聚合(Metric Aggregation)管道聚合(Pipline Aggregation)。本文主要讲讲桶聚合(Bucket Aggregation)

💛聚合的引入

我们在SQL结果中常有:

  1. SELECT COUNT(color)
  2. FROM table
  3. GROUP BY color

ElasticSearch中在概念上类似于 SQL 的分组(GROUP BY),而指标则类似于 COUNT()SUM()MAX() 等统计方法。

进而引入了两个概念:

  • 桶(Buckets) 满足特定条件的文档的集合
  • 指标(Metrics) 对桶内的文档进行统计计算

所以ElasticSearch包含3种聚合(Aggregation)方式

  • 桶聚合(Bucket Aggregation) - 本文中详解

  • 指标聚合(Metric Aggregation) - 下文中讲解

  • 管道聚合(Pipline Aggregation)
    - 再下一篇讲解

    • 聚合管道化,简单而言就是上一个聚合的结果成为下个聚合的输入;

(PS:指标聚合和桶聚合很多情况下是组合在一起使用的,其实你也可以看到,桶聚合本质上是一种特殊的指标聚合,它的聚合指标就是数据的条数count)

💛 按知识点学习聚合

我们先按照官方权威指南中的一个例子,学习Aggregation中的知识点。

1️⃣ 准备数据

让我们先看一个例子。我们将会创建一些对汽车经销商有用的聚合,数据是关于汽车交易的信息:车型、制造商、售价、何时被出售等。

首先我们批量索引一些数据:

  1. POST /test-agg-cars/_bulk
  2. { "index": {}}
  3. { "price" : 10000, "color" : "red", "make" : "honda", "sold" : "2014-10-28" }
  4. { "index": {}}
  5. { "price" : 20000, "color" : "red", "make" : "honda", "sold" : "2014-11-05" }
  6. { "index": {}}
  7. { "price" : 30000, "color" : "green", "make" : "ford", "sold" : "2014-05-18" }
  8. { "index": {}}
  9. { "price" : 15000, "color" : "blue", "make" : "toyota", "sold" : "2014-07-02" }
  10. { "index": {}}
  11. { "price" : 12000, "color" : "green", "make" : "toyota", "sold" : "2014-08-19" }
  12. { "index": {}}
  13. { "price" : 20000, "color" : "red", "make" : "honda", "sold" : "2014-11-05" }
  14. { "index": {}}
  15. { "price" : 80000, "color" : "red", "make" : "bmw", "sold" : "2014-01-01" }
  16. { "index": {}}
  17. { "price" : 25000, "color" : "blue", "make" : "ford", "sold" : "2014-02-12" }

2️⃣ 标准的聚合

有了数据,开始构建我们的第一个聚合。汽车经销商可能会想知道哪个颜色的汽车销量最好,用聚合可以轻易得到结果,用 terms 桶操作:

  1. GET /test-agg-cars/_search
  2. {
  3. "size": 0,
  4. "aggs": {
  5. "popular_colors": {
  6. "terms": {
  7. "field": "color.keyword"
  8. }
  9. }
  10. }
  11. }
  1. 聚合操作被置于顶层参数 aggs 之下(如果你愿意,完整形式 aggregations 同样有效)。
  2. 然后,可以为聚合指定一个我们想要名称,本例中是: popular_colors 。
  3. 最后,定义单个桶的类型 terms 。

结果如下:

ElasticSearch-09-聚合查询之Bucket聚合 - 图1

  1. 因为我们设置了 size 参数,所以不会有 hits 搜索结果返回。
  2. popular_colors 聚合是作为 aggregations 字段的一部分被返回的。
  3. 每个桶的 key 都与 color 字段里找到的唯一词对应。它总会包含 doc_count 字段,告诉我们包含该词项的文档数量。
  4. 每个桶的数量代表该颜色的文档数量。

3️⃣ 多个聚合

同时计算两种桶的结果:对color和对make。

  1. GET /test-agg-cars/_search
  2. {
  3. "size": 0,
  4. "aggs": {
  5. "popular_colors": {
  6. "terms": {
  7. "field": "color.keyword"
  8. }
  9. },
  10. "make_by": {
  11. "terms": {
  12. "field": "make.keyword"
  13. }
  14. }
  15. }
  16. }

结果如下:

ElasticSearch-09-聚合查询之Bucket聚合 - 图2

4️⃣ 聚合的嵌套

这个新的聚合层让我们可以将 avg 度量嵌套置于 terms 桶内。实际上,这就为每个颜色生成了平均价格。

  1. GET /test-agg-cars/_search
  2. {
  3. "size" : 0,
  4. "aggs": {
  5. "colors": {
  6. "terms": {
  7. "field": "color.keyword"
  8. },
  9. "aggs": {
  10. "avg_price": {
  11. "avg": {
  12. "field": "price"
  13. }
  14. }
  15. }
  16. }
  17. }
  18. }

结果如下:

ElasticSearch-09-聚合查询之Bucket聚合 - 图3

正如 颜色 的例子,我们需要给度量起一个名字( avg_price )这样可以稍后根据名字获取它的值。最后,我们指定度量本身( avg )以及我们想要计算平均值的字段( price )

5️⃣ 动态脚本的聚合

高版本才有runtime_mappings,7.6.3以前都没有

这个例子告诉你,ElasticSearch还支持一些基于脚本(生成运行时的字段)的复杂的动态聚合。

  1. GET /test-agg-cars/_search
  2. {
  3. "runtime_mappings": {
  4. "make.length": {
  5. "type": "long",
  6. "script": "emit(doc['make.keyword'].value.length())"
  7. }
  8. },
  9. "size" : 0,
  10. "aggs": {
  11. "make_length": {
  12. "histogram": {
  13. "interval": 1,
  14. "field": "make.length"
  15. }
  16. }
  17. }
  18. }

结果如下:

ElasticSearch-09-聚合查询之Bucket聚合 - 图4

histogram可以参考后文内容。

💛 按分类学习Bucket聚合

我们在具体学习时,也无需学习每一个点,基于上面图的认知,我们只需用20%的时间学习最为常用的80%功能即可,其它查查文档而已。

1️⃣ 前置条件的过滤:filter

在当前文档集上下文中定义与指定过滤器(Filter)匹配的所有文档的单个存储桶。通常,这将用于将当前聚合上下文缩小到一组特定的文档。

  1. GET /test-agg-cars/_search
  2. {
  3. "size": 0,
  4. "aggs": {
  5. "make_by": {
  6. "filter": {
  7. "term": {
  8. "make": "honda"
  9. }
  10. },
  11. "aggs": {
  12. "avg_price": {
  13. "avg": {
  14. "field": "price"
  15. }
  16. }
  17. }
  18. }
  19. }
  20. }

结果如下:

ElasticSearch-09-聚合查询之Bucket聚合 - 图5

2️⃣ 对filter进行分组聚合:filters

设计一个新的例子, 日志系统中,每条日志都是在文本中,包含warning/info等信息。

  1. PUT /test-agg-logs/_bulk?refresh
  2. {"index":{"_id":1}}
  3. {"body":"warning: page could not be rendered"}
  4. {"index":{"_id":2}}
  5. {"body":"authentication error"}
  6. {"index":{"_id":3}}
  7. {"body":"warning: connection timed out"}
  8. {"index":{"_id":4}}
  9. {"body":"info: hello pdai"}

我们需要对包含不同日志类型的日志进行分组,这就需要filters:

  1. GET /test-agg-logs/_search
  2. {
  3. "size": 0,
  4. "aggs": {
  5. "messages": {
  6. "filters": {
  7. "other_bucket_key": "other_messages",
  8. "filters": {
  9. "infos": {
  10. "match": {
  11. "body": "info"
  12. }
  13. },
  14. "warnings": {
  15. "match": {
  16. "body": "warning"
  17. }
  18. }
  19. }
  20. }
  21. }
  22. }
  23. }

结果如下:

ElasticSearch-09-聚合查询之Bucket聚合 - 图6

3️⃣ 对number类型聚合:Range

基于多桶值源的聚合,使用户能够定义一组范围-每个范围代表一个桶。在聚合过程中,将从每个存储区范围中检查从每个文档中提取的值,并“存储”相关/匹配的文档。请注意,此聚合包括from值,但不包括to每个范围的值。

  1. GET /test-agg-cars/_search
  2. {
  3. "size": 0,
  4. "aggs": {
  5. "price_ranges": {
  6. "range": {
  7. "field": "price",
  8. "ranges": [
  9. {
  10. "to": 20000
  11. },
  12. {
  13. "from": 20000,
  14. "to": 40000
  15. },
  16. {
  17. "from": 40000
  18. }
  19. ]
  20. }
  21. }
  22. }
  23. }

结果如下:

ElasticSearch-09-聚合查询之Bucket聚合 - 图7

4️⃣ 对IP类型聚合:IP Range

专用于IP值的范围聚合。

  1. GET /ip_addresses/_search
  2. {
  3. "size": 10,
  4. "aggs": {
  5. "ip_ranges": {
  6. "ip_range": {
  7. "field": "ip",
  8. "ranges": [
  9. { "to": "10.0.0.5" },
  10. { "from": "10.0.0.5" }
  11. ]
  12. }
  13. }
  14. }
  15. }

返回

  1. {
  2. ...
  3. "aggregations": {
  4. "ip_ranges": {
  5. "buckets": [
  6. {
  7. "key": "*-10.0.0.5",
  8. "to": "10.0.0.5",
  9. "doc_count": 10
  10. },
  11. {
  12. "key": "10.0.0.5-*",
  13. "from": "10.0.0.5",
  14. "doc_count": 260
  15. }
  16. ]
  17. }
  18. }
  19. }
  • CIDR Mask分组

此外还可以用CIDR Mask分组

  1. GET /ip_addresses/_search
  2. {
  3. "size": 0,
  4. "aggs": {
  5. "ip_ranges": {
  6. "ip_range": {
  7. "field": "ip",
  8. "ranges": [
  9. { "mask": "10.0.0.0/25" },
  10. { "mask": "10.0.0.127/25" }
  11. ]
  12. }
  13. }
  14. }
  15. }

返回

  1. {
  2. ...
  3. "aggregations": {
  4. "ip_ranges": {
  5. "buckets": [
  6. {
  7. "key": "10.0.0.0/25",
  8. "from": "10.0.0.0",
  9. "to": "10.0.0.128",
  10. "doc_count": 128
  11. },
  12. {
  13. "key": "10.0.0.127/25",
  14. "from": "10.0.0.0",
  15. "to": "10.0.0.128",
  16. "doc_count": 128
  17. }
  18. ]
  19. }
  20. }
  21. }
  • 增加key显示
  1. GET /ip_addresses/_search
  2. {
  3. "size": 0,
  4. "aggs": {
  5. "ip_ranges": {
  6. "ip_range": {
  7. "field": "ip",
  8. "ranges": [
  9. { "to": "10.0.0.5" },
  10. { "from": "10.0.0.5" }
  11. ],
  12. "keyed": true // here
  13. }
  14. }
  15. }
  16. }

返回

  1. {
  2. ...
  3. "aggregations": {
  4. "ip_ranges": {
  5. "buckets": {
  6. "*-10.0.0.5": {
  7. "to": "10.0.0.5",
  8. "doc_count": 10
  9. },
  10. "10.0.0.5-*": {
  11. "from": "10.0.0.5",
  12. "doc_count": 260
  13. }
  14. }
  15. }
  16. }
  17. }
  • 自定义key显示
  1. GET /ip_addresses/_search
  2. {
  3. "size": 0,
  4. "aggs": {
  5. "ip_ranges": {
  6. "ip_range": {
  7. "field": "ip",
  8. "ranges": [
  9. { "key": "infinity", "to": "10.0.0.5" },
  10. { "key": "and-beyond", "from": "10.0.0.5" }
  11. ],
  12. "keyed": true
  13. }
  14. }
  15. }
  16. }

返回

  1. {
  2. ...
  3. "aggregations": {
  4. "ip_ranges": {
  5. "buckets": {
  6. "infinity": {
  7. "to": "10.0.0.5",
  8. "doc_count": 10
  9. },
  10. "and-beyond": {
  11. "from": "10.0.0.5",
  12. "doc_count": 260
  13. }
  14. }
  15. }
  16. }
  17. }

5️⃣ 对日期类型聚合:Date Range

专用于日期值的范围聚合。

  1. GET /test-agg-cars/_search
  2. {
  3. "size": 0,
  4. "aggs": {
  5. "range": {
  6. "date_range": {
  7. "field": "sold",
  8. "format": "yyyy-MM-dd",
  9. "ranges": [
  10. {
  11. "from": "2014-01-01"
  12. },
  13. {
  14. "to": "2014-12-31"
  15. }
  16. ]
  17. }
  18. }
  19. }
  20. }

结果如下:

ElasticSearch-09-聚合查询之Bucket聚合 - 图8

此聚合与Range聚合之间的主要区别在于 from和to值可以在Date Math表达式中表示,并且还可以指定日期格式,通过该日期格式将返回from and to响应字段。请注意,此聚合包括from值,但不包括to每个范围的值

6️⃣ 对柱状图功能:Histrogram

直方图 histogram 本质上是就是为柱状图功能设计的。

创建直方图需要指定一个区间,如果我们要为售价创建一个直方图,可以将间隔设为 20,000。这样做将会在每个 $20,000 档创建一个新桶,然后文档会被分到对应的桶中。

对于仪表盘来说,我们希望知道每个售价区间内汽车的销量。我们还会想知道每个售价区间内汽车所带来的收入,可以通过对每个区间内已售汽车的售价求和得到。

可以用 histogram 和一个嵌套的 sum 度量得到我们想要的答案:

  1. GET /test-agg-cars/_search
  2. {
  3. "size": 0,
  4. "aggs": {
  5. "price": {
  6. "histogram": {
  7. "field": "price",
  8. "interval": 20000
  9. },
  10. "aggs": {
  11. "revenue": {
  12. "sum": {
  13. "field": "price"
  14. }
  15. }
  16. }
  17. }
  18. }
  19. }
  1. histogram 桶要求两个参数:一个数值字段以及一个定义桶大小间隔。
  2. sum 度量嵌套在每个售价区间内,用来显示每个区间内的总收入。

如我们所见,查询是围绕 price 聚合构建的,它包含一个 histogram 桶。它要求字段的类型必须是数值型的同时需要设定分组的间隔范围。 间隔设置为 20,000 意味着我们将会得到如 [0-19999, 20000-39999, …] 这样的区间。

接着,我们在直方图内定义嵌套的度量,这个 sum 度量,它会对落入某一具体售价区间的文档中 price 字段的值进行求和。 这可以为我们提供每个售价区间的收入,从而可以发现到底是普通家用车赚钱还是奢侈车赚钱。

响应结果如下:

ElasticSearch-09-聚合查询之Bucket聚合 - 图9

结果很容易理解,不过应该注意到直方图的键值是区间的下限。键 0 代表区间 0-19,999 ,键 20000 代表区间 20,000-39,999 ,等等。

ElasticSearch-09-聚合查询之Bucket聚合 - 图10

当然,我们可以为任何聚合输出的分类和统计结果创建条形图,而不只是 直方图 桶。让我们以最受欢迎 10 种汽车以及它们的平均售价、标准差这些信息创建一个条形图。 我们会用到 terms 桶和 extended_stats 度量:

  1. GET /test-agg-cars/_search
  2. {
  3. "size" : 0,
  4. "aggs": {
  5. "makes": {
  6. "terms": {
  7. "field": "make.keyword",
  8. "size": 10
  9. },
  10. "aggs": {
  11. "stats": {
  12. "extended_stats": {
  13. "field": "price"
  14. }
  15. }
  16. }
  17. }
  18. }
  19. }

上述代码会按受欢迎度返回制造商列表以及它们各自的统计信息。我们对其中的 stats.avg 、 stats.count 和 stats.std_deviation 信息特别感兴趣,并用 它们计算出标准差:

  1. std_err = std_deviation / count

ElasticSearch-09-聚合查询之Bucket聚合 - 图11

对应报表:

ElasticSearch-09-聚合查询之Bucket聚合 - 图12

文件转载