elasticsearch 聚合 (一) 桶、指标

一 高阶概念
桶(Buckets)
满足特定条件的文档的集合

桶 简单来说就是满足特定条件的文档的集合:
一个雇员属于 男性 桶或者 女性 桶

奥尔巴尼属于 纽约 桶
日期2014-10-28属于 十月 桶

例如,浦东会被放入上海这个桶,而 整个上海桶会被放入中国这个桶。
elasticsearch 有很多种类型的桶,能让你通过很多种方式来划分文档(时间、最受欢迎的词、年龄区间、地理位置等等)。其实根本上都是通过同样的原理进行操作:基于条件来划分文档

指标(Metrics)

对桶内的文档进行统计计算
桶能让我们划分文档到有意义的集合,但是最终我们需要的是对这些桶内的文档进行一些指标的计算。分桶是一种达到目的的手段:它提供了一种给文档分组的方法来让我们可以计算感兴趣的指标。
大多数 指标 是简单的数学运算(例如最小值、平均值、最大值,还有汇总),这些是通过文档的值来计算。在实践中,指标能让你计算像平均薪资、最高出售价格、95%的查询延迟这样的数据

形象化一点就是SQL的count(),SUM(),MAX()等统计方法相当于指标,而GROUP BY color 相当于桶

聚合(aggregation)
聚合 是由桶和指标组成的。 聚合可能只有一个桶,可能只有一个指标,或者可能两个都有。也有可能有一些桶嵌套在其他桶里面。例如,我们可以通过所属国家来划分文档(桶),然后计算每个国家的平均薪酬(指标)
由于桶可以被嵌套,我们可以实现非常多并且非常复杂的聚合:
1.通过国家划分文档(桶)
2.然后通过性别划分每个国家(桶)
3.然后通过年龄区间划分每种性别(桶)
4.最后,为每个年龄区间计算平均薪酬(指标)
最后将告诉你每个 <国家, 性别, 年龄> 组合的平均薪酬。所有的这些都在一个请求内完成并且只遍历一次数据!

光说不练假把式,所以让我们先看一个例子。我们将会创建一些对家电经销商有用的聚合,数据是关家电交易的信息:颜色、制造商、售价、何时被出售等
准备数据:

`PUT /tvs
{
  "mappings": {
    "properties": {
      "price": {
        "type": "long"
      },
      "color": {
        "type": "keyword"
      },
      "brand": {
        "type": "keyword"
      },
      "sold_date": {
        "type": "date"
      }
    }
  }
}
#添加数据
POST /tvs/_bulk
{"index":{}}
{"price":1000,"color":"红色","brand":"长虹","sold_date":"2016-10-28"}
{"index":{}}
{"price":2000,"color":"红色","brand":"长虹","sold_date":"2016-11-05"}
{"index":{}}
{"price":3000,"color":"绿色","brand":"小米","sold_date":"2016-05-18"}
{"index":{}}
{"price":1500,"color":"蓝色","brand":"TCL","sold_date":"2016-07-02"}
{"index":{}}
{"price":1200,"color":"绿色","brand":"TCL","sold_date":"2016-08-19"}
{"index":{}}
{"price":2000,"color":"红色","brand":"长虹","sold_date":"2016-11-05"}
{"index":{}}
{"price":8000,"color":"红色","brand":"三星","sold_date":"2017-01-01"}
{"index":{}}
{"price":2500,"color":"蓝色","brand":"小米","sold_date":"2017-02-12"}
{"index":{}}
{"price":1999,"color":"红色","brand":"长虹","sold_date":"2021-10-28"}

按颜色分组统计电视机销量

GET /tvs/_search
{
  "size": 0,
  "aggs": {
    "love_colors": {
      "terms": {
        "field": "color"
      }
    }
  }
}

size:0代表只获取聚合结果,而不要执行聚合的原始数据,结果集里面的hits.hits是[]
aggs:固定语法,要对一份数据执行分组聚合操作
popular_colors:就是对每个aggs,都要起一个名字,这个名字是随机的,你随便取什么都ok
terms:根据字段的值进行分组
field:根据指定的字段的值进行分组

{
  "took" : 0,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 8,
      "relation" : "eq"
    },
    "max_score" : null,
    "hits" : [ ] # 注意这里是空 因为size设置为零了
  },
  "aggregations" : {
    "love_colors" : {
      "doc_count_error_upper_bound" : 0,
      "sum_other_doc_count" : 0,
      "buckets" : [
        {
          "key" : "红色",
          "doc_count" : 4
        },
        {
          "key" : "绿色",
          "doc_count" : 2
        },
        {
          "key" : "蓝色",
          "doc_count" : 2
        }
      ]
    }
  }
}

hits.hits:指定size=0,所以hits.hits就是空的,否则会把执行聚合的那些原始数据返回回来
aggregations:聚合结果
love_colors: 指定某个聚合的名称
buckets:指定的field划分出的buckets
key:每个bucket对应的那个值
doc_count: 每个bucket分组内,有多少个数据,每种颜色对应的bucket中的数据的
默认的排序规则:doc_count 降序排序

按颜色分组,平均avg价格

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

其结果集最终会变成如下:
{
"key" : "红色",
"doc_count" : 4,
"avg_price" : {
"value" : 3250.0
}
},

有了平均值,还要有最大,最小,求和

#query
{
  "size": 0,
  "aggs": {
    "colors": {
      "terms": {
        "field": "color"
      },
      "aggs": {
        "avg_price": {
          "avg": {
            "field": "price"
          }
        },
        "min_price": {
          "min": {
            "field": "price"
          }
        },
        "max_price": {
          "max": {
            "field": "price"
          }
        },
        "sum_price": {
          "sum": {
            "field": "price"
          }
        }
      }
    }
  }
}

#result
{
          "key" : "红色",
          "doc_count" : 4,
          "max_price" : {
            "value" : 8000.0
          },
          "min_price" : {
            "value" : 1000.0
          },
          "avg_price" : {
            "value" : 3250.0
          },
          "sum_price" : {
            "value" : 13000.0
          }
        }

在颜色区分的时候,再把相关品牌展示出来,这个就涉及到桶的嵌套

#query
{
  "size": 0,
  "aggs": {
    "colors": {
      "terms": {
        "field": "color"
      },
      "aggs": {
        "avg_price": {
          "avg": {
            "field": "price"
          }
        },
        "brand": {
          "terms": {
            "field": "brand"
          }
        }
      }
    }
  }
}

#result
{
    "key" : "红色",
    "doc_count" : 4,
    "avg_price" : {
        "value" : 3250.0
    },
    "brand" : {
        "doc_count_error_upper_bound" : 0,
        "sum_other_doc_count" : 0,
        "buckets" : [
            {
            "key" : "长虹",
            "doc_count" : 3
            },
            {
            "key" : "三星",
            "doc_count" : 1
            }
         ]
        }
    }

往往现实中需求很写实,所以还有铵价格区间统计销售量和销售额(直方图)
histogram 也是bucket ,他按照某个值指定的interval(步长),划分一个一个的bucket
interval:2000,0~1999,2000~3999,4000~5999,6000~7999,8000~9999,buckets
去根据price的值2500,此时就会将这条数据放入2000~4000对应的那个bucket中

我们上面这条数据{"price":1999,"color":"红色","brand":"长虹","sold_date":"2021-10-28"}
用来测试是2000 还是1999

GET /tvs/_search

#query
{
  "size": 0,
  "aggs": {
    "price": {
      "histogram": { #类型为n. [统计] 直方图;柱状图
        "field": "price",
        "interval": 2000
      },
      "aggs": {
        "revenue": {
          "sum": {
            "field": "price"
          }
        }
      }
    }
  }
}

#result
 "aggregations" : {
    "price" : {
      "buckets" : [
        {
          "key" : 0.0,
          "doc_count" : 4,
          "revenue" : {
            "value" : 5699.0
          }
        },
        {
          "key" : 2000.0,
          "doc_count" : 4,
          "revenue" : {
            "value" : 9500.0 # 这里的值代表2000-3999价格区间的累加
          }
        },
        {
          "key" : 4000.0,
          "doc_count" : 0,
          "revenue" : {
            "value" : 0.0
          }
        },
        {
          "key" : 6000.0,
          "doc_count" : 0,
          "revenue" : {
            "value" : 0.0
          }
        },
        {
          "key" : 8000.0,
          "doc_count" : 1,
          "revenue" : {
            "value" : 8000.0
          }
        }
      ]
    }
  }

销售中很常见的一种场景,按照年度、季度、月份、周来统计销售量,那就要用到calender_interval
calender_interval =1m 那间隔就是一个月 d一天 w一周 y 一年 h 小时 季度 quarter
这里间隔是每月一个区间

`#query
{
  "size": 0,
  "aggs": {
    "sales": {
      "date_histogram": {
        "field": "sold_date",
        "calendar_interval":"1w", 
        "format": "yyyy-mm-dd"
      }
    }
  }
}

#result
 "aggregations" : {
    "sales" : {
      "buckets" : [
        {
          "key_as_string" : "2016-00-16",
          "key" : 1463356800000,
          "doc_count" : 1
        },
        {
          "key_as_string" : "2016-00-23",
          "key" : 1463961600000,
          "doc_count" : 0
        },

`
上述结果会把doc_count 为零的也显示出来,这样就满足了一些顽固产品的需求 即这周没有销售额

也要给我显示出来。如果不想显示出来 则在请求的时候增加 min_doc_count

{
    "query":{
        "size":0,
        "aggs":{
            "sales":{
                "date_histogram":{
                    "field":"sold_date",
                    "calender_interval":"quarter",
                     "format": "yyyy-mm-dd",
                     "min_doc_count":1 # 最少有1个才返回
                }
            }
        }
    }
}
#result
  {
  "key_as_string" : "2016-04-01",
  "key" : 1459468800000,
  "doc_count" : 1
  },`
  {
  "key_as_string" : "2016-07-01",
  "key" : 1467331200000,
  "doc_count" : 2
  },
  {
  "key_as_string" : "2016-10-01",
  "key" : 1475280000000,
  "doc_count" : 3
  },`

假如有个产品告诉你,及时在某个时间范围内没有搜索到,你也要展示出来,那你就要用
"extended_bounds": { "min": "2016-01-01", "max": "2017-12-31" }

写在min_doc_count 同级的位置,这样即使数据集里没有2016年第一季度的数据,仍然会
有一条doc_count为0的数据

    {
      "key_as_string" : "2016-01-01",
      "key" : 1451606400000,
      "doc_count" : 0 # 就这条
    },
    {
      "key_as_string" : "2016-04-01",
      "key" : 1459468800000,
      "doc_count" : 1
    }

但是如果同时设置min_doc_count 大于 0 和extended_bounds 优先级较高的是前者.

我们构建聚合以便按季度展示所有家电品牌总销售额。同时按季度、按每个家电品牌计算销售总额,以便可以找出哪种品牌最赚钱(出了点岔子,密码不对了重置了下密码)

#query
{
  "size": 0,
  "aggs": {
    "sales": {
      "date_histogram": {
        "field": "sold_date",
        "calendar_interval": "quarter",
        "format": "yyyy-MM-dd",
        "min_doc_count": 1,
        "extended_bounds": {
          "min": "now/d",
          "max": "now/d"
        }
      },
      "aggs": {
        "per_make_sum": {
          "terms": {
            "field": "brand",
            "size": 10
          },
          "aggs": {
            "sum_price": {
              "sum": {
                "field": "price" #计算每种品牌的总销售金额
              }
            }
          }
        },
        "total_sum": {
          "sum": {
            "field": "price" #也计算所有全部品牌的汇总销售金额。
          }
        }
      }
    }
  }
}
#result
 {
 "key_as_string" : "2017-01-01",
 "key" : 1483228800000,
 "doc_count" : 2,
 "per_make_sum" : {
 "doc_count_error_upper_bound" : 0,
 "sum_other_doc_count" : 0,
 "buckets" : [
 {
 "key" : "三星",
 "doc_count" : 1,
 "sum_price" : {
 "value" : 8000.0
 }
 },
 {
 "key" : "小米",
 "doc_count" : 1,
 "sum_price" : {
 "value" : 2500.0
 }
 }
 ]
 },
 "total_sum" : {
 "value" : 10500.0
 }
 },

快结束了,再坚持一下哦。桶的嵌套,让家电按照颜色来分组然后计算其平均价格,再按品牌分组,再计算其平均价格

#query
{
  "size": 0,
  "aggs": {
    "group_by_color": {
      "terms": {
        "field": "color"
      },
      "aggs": {
        "color_avg_price": {
          "avg": {
            "field": "price"
          }
        },
        "group_by_brand": {
          "terms": {
            "field": "brand"
          },
          "aggs": {
            "brand_avg_price": {
              "avg": {
                "field": "price"
              }
            }
          }
        }
      }
    }
  }
}
#result
    {
          "key" : "绿色",
          "doc_count" : 2,
          "color_avg_price" : {
            "value" : 2100.0
          },
          "group_by_brand" : {
            "doc_count_error_upper_bound" : 0,
            "sum_other_doc_count" : 0,
            "buckets" : [
              {
                "key" : "TCL",
                "doc_count" : 1,
                "brand_avg_price" : {
                  "value" : 1200.0
                }
              },
              {
                "key" : "小米",
                "doc_count" : 1,
                "brand_avg_price" : {
                  "value" : 3000.0
                }
              }
            ]
          }
        }

参考链接:https://blog.csdn.net/ma_jiang/article/details/113763584

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注