写点什么

Elasticsearch 聚合学习之二:区间聚合

作者:程序员欣宸
  • 2022 年 9 月 14 日
    广东
  • 本文字数:6161 字

    阅读完需:约 20 分钟

Elasticsearch聚合学习之二:区间聚合

欢迎访问我的 GitHub

这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos

本篇概览

  • 本文是《Elasticsearch 聚合学习》系列的第二篇,上一篇是我们熟悉了聚合的基本操作,本篇的内容是按照区间聚合的实战操作;

系列文章列表

  1. 《Elasticsearch聚合学习之一:基本操作》

  2. 《Elasticsearch聚合学习之二:区间聚合》

  3. 《Elasticsearch聚合学习之三:范围限定》;

  4. 《Elasticsearch聚合学习之四:结果排序》

环境信息

  • 以下是本次实战的环境信息,请确保您的 Elasticsearch 可以正常运行:


  1. 操作系统:Ubuntu 18.04.2 LTS

  2. JDK:1.8.0_191

  3. Elasticsearch:6.7.1

  4. Kibana:6.7.1


  • 实战用的数据依然是一些汽车销售的记录,在第一章有详细的导入步骤,请参考操作,导入后您的 es 中的数据如下图:

本章概要

  • 本篇实战的聚合操作有以下内容:


  1. 指定字段的区间聚合;

  2. 时间字段的区间聚合;

  3. 扩展实战;


  • 接下来开始实战吧。

条形图(histogram 桶)

  • 还记得 terms 桶么,用来将指定字段值相同的文档聚合在一个桶中,而 histogram 桶是将指定字段值在某个范围内的文档聚合在一个桶中,如下图所示,0-19999 是一个桶,11000 和 15000 在一个桶内,23000 和 31000 在一个桶内,这就是 histogram 桶:


  • 以汽车销售记录为例做一次聚合查询,为售价创建 histogram 桶,以 20000 作为间隔,每个桶负责的区间如上图所示,相关的销售记录就会被放入对应的桶中,请求参数和说明如下:


GET /cars/transactions/_search{  "size":0,                  ---令返回值的hits对象为空  "aggs":{                   ---聚合命令   "price":{                 ---聚合字段名称     "histogram": {          ---桶类型       "field": "price",     ---指定price字段的值作为判断条件       "interval": 20000     ---每个桶负责的区间大小为20000     }   }   }}
复制代码


  • es 返回的数据和说明如下:


{  "took" : 57,  "timed_out" : false,  "_shards" : {    "total" : 5,    "successful" : 5,    "skipped" : 0,    "failed" : 0  },  "hits" : {    "total" : 8,    "max_score" : 0.0,    "hits" : [ ]  },  "aggregations" : {             ---聚合结果    "price" : {                  ---请求参数中指定的名称      "buckets" : [              ---price桶的数据在此数组中        {          "key" : 0.0,           ---第一个桶,区间[0-19999],0.0是起始值          "doc_count" : 3        ---这个区间有三个文档(price值分别是10000、12000、15000)        },        {          "key" : 20000.0,       ---第二个桶,区间[20000-39999],20000.0是起始值          "doc_count" : 4        ---这个区间有四个文档        },        {          "key" : 40000.0,       ---第三个桶,区间[40000-59999],40000.0是起始值          "doc_count" : 0        ---这个区间没有文档        },       ......
复制代码

控制空桶是否返回

  • 在上面的返回值中,第三个桶中没有文档,在有的业务场景中,我们不需要没有数据的桶,此时可以用 min_doc_count 参数来控制,如果 min_doc_count 等于 2,表示桶中最少有两条记录才会出现在返回内容中,如下所示,min_doc_count 如果等于 1,那么空桶就不会被 es 返回了:


GET /cars/transactions/_search{  "size":0,  "aggs":{   "price":{     "histogram": {       "field": "price",       "interval": 20000,       "min_doc_count": 1     }   }   }}
复制代码


  • 返回值如下所示,没有文档的桶不再出现:


{  "took" : 16,  "timed_out" : false,  "_shards" : {    "total" : 5,    "successful" : 5,    "skipped" : 0,    "failed" : 0  },  "hits" : {    "total" : 8,    "max_score" : 0.0,    "hits" : [ ]  },  "aggregations" : {    "price" : {      "buckets" : [        {          "key" : 0.0,          "doc_count" : 3        },        {          "key" : 20000.0,          "doc_count" : 4        },        {          "key" : 80000.0,          "doc_count" : 1        }      ]    }  }}
复制代码

histogram 桶加 metrics

  • 上面的例子返回结果只有每个桶内的文档数,也可以加入 metrics 对桶中的数据进行处理,例如计算每个区间内的最高价、最低价、平均售价,可以加入 max、min、avg 参数,如下:


GET /cars/transactions/_search{  "size":0,                  ---令返回值的hits对象为空  "aggs":{                   ---聚合命令   "price":{                 ---聚合字段名称     "histogram": {          ---桶类型       "field": "price",     ---指定price字段的值作为判断条件       "interval": 20000     ---每个桶负责的区间大小为20000     },     "aggs": {               ---表示对桶内数据做metrics        "max_price": {       ---指定metrics处理结果的字段名          "max":{            ---metrics类型为max            "field": "price" ---指定取price字段的值做最大值比较          }        },        "min_price": {       ---指定metrics处理结果的字段名          "min":{            ---metrics类型为min            "field": "price" ---指定取price字段的值做最小值比较          }        },        "avg_price": {       ---指定metrics处理结果的字段名          "avg":{            ---metrics类型为avg            "field": "price" ---指定取price字段的值计算平均值          }        }      }   }   }}
复制代码


  • es 返回数据和说明如下,可见每个桶中的文档都做了三种 metrics 处理:


{  "took" : 17,  "timed_out" : false,  "_shards" : {    "total" : 5,    "successful" : 5,    "skipped" : 0,    "failed" : 0  },  "hits" : {    "total" : 8,    "max_score" : 0.0,    "hits" : [ ]  },  "aggregations" : {             ---聚合结果    "price" : {                  ---请求参数中指定的名称      "buckets" : [              ---price桶的数据在此数组中        {          "key" : 0.0,           ---第一个区间[0-19999],0.0是起始值          "doc_count" : 3,       ---这个区间有三条记录(price值分别是10000、12000、15000)          "max_price" : {        ---指定的metrics结果名称            "value" : 15000.0    ---桶中有三个文档,price字段的最大值是15000          },          "min_price" : {            "value" : 10000.0    ---桶中有三个文档,price字段的最小值是10000          },          "avg_price" : {            "value" : 12333.333333333334    ---桶中有三个文档,price字段的平均值是12333.333333333334          }        },       ......
复制代码

时间区间的桶(date_histogram)

  • 按照时间区间聚合也是常用的功能,例如在 ELK 上查询日志,通常都是按照时间来分段的,如下图:



  • histogram 桶可以实现按照时间分段么?如果用毫秒数来处理,似乎是可以的,但是对年月日的处理就力不从心了,常见的时间区间处理,用 date_histogram 桶即可满足要求;

  • 下面就是 date_histogram 桶的用法:每月销售多少台汽车:


GET /cars/transactions/_search{  "size": 0,                    ---令返回值的hits对象为空  "aggs": {                     ---聚合命令    "sales": {                  ---聚合字段名称      "date_histogram": {       ---桶类型        "field": "sold",        ---用sold字段的值作进行时间区间判断        "interval": "month",    ---间隔单位是月        "format": "yyyy-MM-dd"  ---返回的数据中,时间字段格式      },      "aggs": {                 ---表示对桶内数据做metrics        "max_price": {          ---指定metrics处理结果的字段名          "max":{               ---metrics类型为max            "field": "price"    ---指定取price字段的值做最大值比较          }        },        "min_price": {          ---指定metrics处理结果的字段名          "min":{               ---metrics类型为min            "field": "price"    ---指定取price字段的值做最小值比较          }        }      }    }  }}
复制代码


  • es 返回数据如下,篇幅所限因此略去了头部和尾部的一些信息,只看关键的:


"aggregations" : {                           ---聚合结果    "sales" : {                              ---请求参数中指定的名称      "buckets" : [                          ---sales桶的数据在此数组中        {          "key_as_string" : "2014-01-01",    ---请求的format参数指定了key的格式          "key" : 1388534400000,             ---真正的时间字段          "doc_count" : 1,                   ---2014年1月份的文档数量          "max_price" : {                    ---2014年1月的文档做了metrics类型为max的处理后,结果在此            "value" : 80000.0                ---2014年1月的文档中,price字段的最大值          },          "min_price" : {                    ---2014年1月的文档做了metrics类型为min的处理后,结果在此            "value" : 80000.0                ---2014年1月的文档中,price字段的最大值          }        },        {          "key_as_string" : "2014-02-01",          "key" : 1391212800000,          "doc_count" : 1,          "max_price" : {            "value" : 25000.0          },          "min_price" : {            "value" : 25000.0          }        },        ......
复制代码


  • 上面的请求是以一个月作为区间的,如果想以其他时间单位作为区间又该怎么做呢?例如 90 天,把 interval 字段写成 90d 即可,其他粒度的时间间隔写法如下表:



  • 注意:年、季度、月、周都的数量只能是 1,其他粒度的数量可以是整数;

  • 例如以 90 天作为区间来聚合,请求参数如下:


GET /cars/transactions/_search{  "size": 0,  "aggs": {    "sales": {      "date_histogram": {        "field": "sold",        "interval": "90d",     -------表示以90天作为间隔        "format": "yyyy-MM-dd"      },      "aggs": {        "max_price": {          "max":{            "field": "price"          }        },        "min_price": {          "min":{            "field": "price"          }        }      }    }  }}
复制代码

date_histogram 的空桶处理

  • date_histogram 也支持 min_doc_count 参数,和 histogram 桶的用法一样,对于下面的请求,es 的响应中不会有空桶:


GET /cars/transactions/_search{  "size": 0,  "aggs": {    "sales": {      "date_histogram": {        "field": "sold",        "interval": "1M",        "format": "yyyy-MM-dd",        "min_doc_count": 1      }    }  }}
复制代码

扩展实战

  • 本篇的最后,来做一个略为复杂的聚合操作:按季度展示每个汽车品牌的销售总额

  • 显然,操作的第一步是按照时间区间做聚合,然后在每个桶中,将文档按照品牌做第二次聚合,第二次聚合的结果也可以理解为多个桶,每个桶中的文档,是某个平台在某个季度的销售总额;

  • 请求如下:


GET /cars/transactions/_search{  "size": 0,                      ---令返回值的hits对象为空  "aggs": {                       ---聚合命令    "sales": {                    ---聚合字段名称      "date_histogram": {         ---桶类型为时间区间        "field": "sold",          ---指定sold字段的值作为判断条件        "interval": "1q",         ---区间间隔为1季度        "format": "yyyy-MM-dd",   ---返回的桶的key,被格式化时的格式        "min_doc_count": 1        ---空桶不返回      },      "aggs": {                   ---第二层桶        "per_make_sum": {         ---聚合字段名称          "terms": {              ---桶类型为terms            "field": "make"       ---按照make字段聚合          },          "aggs": {               ---第二层桶的metrics            "sum_price": {        ---聚合字段名称              "sum": {            ---metrics处理,累加                "field": "price"  ---取price字段的值累加              }            }          }        }      }    }  }}
复制代码


  • 收到响应如下:


"aggregations" : {    "sales" : {      "buckets" : [                             ---聚合结果        {          "key_as_string" : "2014-01-01",       ---当前桶的key的格式化后的值          "key" : 1388534400000,                ---当前桶的key原值          "doc_count" : 2,                      ---当前桶中文档数          "per_make_sum" : {                    ---第二层桶的名称            "doc_count_error_upper_bound" : 0,            "sum_other_doc_count" : 0,            "buckets" : [                       ---第二层聚合结果              {                "key" : "bmw",                  ---聚合字段的值,这里是汽车品牌                "doc_count" : 1,                ---桶内的文档数量                "sum_price" : {                 ---metrics处理结果名称                  "value" : 80000.0             ---metrics处理结果,这里是销售额累加值                }              },              {                "key" : "ford",                 ---聚合字段的值,这里是汽车品牌                "doc_count" : 1,                ---桶内的文档数量                "sum_price" : {                 ---metrics处理结果名称                  "value" : 25000.0             ---metrics处理结果,这里是销售额累加值                }              }            ]          }        },        {          "key_as_string" : "2014-04-01",          "key" : 1396310400000,          "doc_count" : 1,          "per_make_sum" : {            "doc_count_error_upper_bound" : 0,            "sum_other_doc_count" : 0,            "buckets" : [              {                "key" : "ford",                "doc_count" : 1,                "sum_price" : {                  "value" : 30000.0                }              }            ]          }        },
复制代码


  • 至此,区间聚合的学习和实战就完成了,到目前为止,我们的操作用的都是索引中的全部数据,但是真是生产环境中,不会每次都用全部数据来做聚合,因此接下来的章节,会将聚合与查询、过滤等操作结合在一起实战;

欢迎关注 InfoQ:程序员欣宸

学习路上,你不孤单,欣宸原创一路相伴...


发布于: 11 小时前阅读数: 3
用户头像

搜索"程序员欣宸",一起畅游Java宇宙 2018.04.19 加入

前腾讯、前阿里员工,从事Java后台工作,对Docker和Kubernetes充满热爱,所有文章均为作者原创,个人Github:https://github.com/zq2599/blog_demos

评论

发布
暂无评论
Elasticsearch聚合学习之二:区间聚合_elasticsearch_程序员欣宸_InfoQ写作社区