还记得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即可,其他粒度的时间间隔写法如下表:
  • 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
    
  • 至此,区间聚合的学习和实战就完成了,到目前为止,我们的操作用的都是索引中的全部数据,但是真是生产环境中,不会每次都用全部数据来做聚合,因此接下来的章节,会将聚合与查询、过滤等操作结合在一起实战;
  • 欢迎关注51CTO博客:程序员欣宸

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