2023-08-08  阅读(312)
原文作者:Ressmix 原文地址:https://www.tpvlog.com/article/146

从本章开始,我将介绍Elasticsearch强大的聚合分析功能,聚合分析和SQL中的GROUP BY非常类似。掌握聚合分析,最重要的还是实战练习,所以从本章开始,我将会通过一个案例,来介绍聚合分析的各种使用方式。

我们先来了解下聚合分析中的两个核心概念:bucket和metric。

一、核心概念

1.1 bucket

bucket代表一个数据分组。举个例子,我们有以下数据:

CITY NAME
北京 小李
北京 小王
上海 小张
上海 小丽
上海 小陈

如果我们基于CITY划分buckets,就划分出来两个bucket:

  • 北京bucket:包含了2个人,小李、小王
  • 上海bucket:包含了3个人,小张、小丽、小陈

1.2 metric

当我们对数据进行bucket分组之后,就可以对每个bucket进行统计分析了,比如说计算一个bucket内所有数据的数量,或者计算一个bucket内所有数据的平均值、最大值、最小值。

metric,就是对一个bucket执行的某种聚合分析操作,比如说求平均值、求最大值/最大值、求数量、请和等。

二、案例背景

我们的案例以家电卖场中的电视销售为背景,这个案例将会贯彻整个聚合分析系列的所有章节。我们会对各种品牌、颜色的电视机销量和销售额进行聚合分析。

首先构建一个索引tvs

    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" }

三、实战

3.1 metric:按数量分组

我们需要统计哪种颜色的电视机销量最高:

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

查询请求的各个关键字含义如下:

  • size: 只获取聚合结果,而不需要返回执行聚合的那些原始数据;
  • aggs: 固定语法,表示要对一份数据执行分组聚合操作;
  • popular_colors: 每个aggs的名字,自定义;
  • terms: 根据字段值进行分组;
  • field: 进行分组的字段。

上述请求的返回结果如下:

    {
      "took" : 7,
      "timed_out" : false,
      "_shards" : {
        "total" : 1,
        "successful" : 1,
        "skipped" : 0,
        "failed" : 0
      },
      "hits" : {
        "total" : {
          "value" : 8,
          "relation" : "eq"
        },
        "max_score" : null,
        "hits" : [ ]
      },
      "aggregations" : {
        "popular_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: 聚合结果;
  • popular_color: 自定义的聚合名称;
  • buckets: 根据我们指定的field划分出的buckets;
  • key: field的值
  • doc_count: 这个bucket分组内的doc条数

按数量分组其实并不算是一个metric操作,它是Elasticsearch对聚合分析的一种默认操作,利用term实现。

3.2 metric:统计平均值

这一节,我们来学习第一个metric操作——统计平均值。假设我们需要统计每种颜色电视机的平均价格:

    GET /tvs/_search
    {
       "size" : 0,
       "aggs": {
          "colors": {
             "terms": {
                "field": "color"
             },
             "aggs": { 
                "avg_price": { 
                   "avg": {
                      "field": "price" 
                   }
                }
             }
          }
       }
    }

上述请求,我们嵌套了一个”aggs“,这个”aggs“和“terms”平级,会对每个bucket执行一次metric操作:

    "aggs": { 
        "avg_price": { 
            "avg": {
              "field": "price" 
            }
        }
    }

返回结果如下:

    {
      "took" : 12,
      "timed_out" : false,
      "_shards" : {
        "total" : 1,
        "successful" : 1,
        "skipped" : 0,
        "failed" : 0
      },
      "hits" : {
        "total" : {
          "value" : 8,
          "relation" : "eq"
        },
        "max_score" : null,
        "hits" : [ ]
      },
      "aggregations" : {
        "colors" : {
          "doc_count_error_upper_bound" : 0,
          "sum_other_doc_count" : 0,
          "buckets" : [
            {
              "key" : "红色",
              "doc_count" : 4,
              "avg_price" : {
                "value" : 3250.0
              }
            },
            {
              "key" : "绿色",
              "doc_count" : 2,
              "avg_price" : {
                "value" : 2100.0
              }
            },
            {
              "key" : "蓝色",
              "doc_count" : 2,
              "avg_price" : {
                "value" : 2000.0
              }
            }
          ]
        }
      }
    }

可以看到,每个bucket内部多了”avg_price“,其value就是我们的metric计算的结果——每个bucket中的所有doc的price字段值的平均值。

3.3 下钻分析

所谓下钻分析,就是对每个bucket再进行分组,然后对每个最小粒度的分组再执行聚合分析操作。比如,我们已经按照颜色对电视机进行分组了,但是还想统计下每种颜色下的各个品牌的电视机平均价格:

    GET /tvs/_search 
    {
      "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"
                  }
                }
              }
            }
          }
        }
      }
    }

可以看到,上述请求在最内部又嵌套了一个"group_by_brand",按照band字段进行分组,然后求品牌的平均价格:

    "group_by_brand": {
      "terms": {
        "field": "brand"
      },
      "aggs": {
        "brand_avg_price": {
          "avg": {
            "field": "price"
          }
        }
      }
    }

返回结果如下:

    {
      "took" : 54,
      "timed_out" : false,
      "_shards" : {
        "total" : 1,
        "successful" : 1,
        "skipped" : 0,
        "failed" : 0
      },
      "hits" : {
        "total" : {
          "value" : 8,
          "relation" : "eq"
        },
        "max_score" : null,
        "hits" : [ ]
      },
      "aggregations" : {
        "group_by_color" : {
          "doc_count_error_upper_bound" : 0,
          "sum_other_doc_count" : 0,
          "buckets" : [
            {
              "key" : "红色",
              "doc_count" : 4,
              "color_avg_price" : {
                "value" : 3250.0
              },
              "group_by_brand" : {
                "doc_count_error_upper_bound" : 0,
                "sum_other_doc_count" : 0,
                "buckets" : [
                  {
                    "key" : "长虹",
                    "doc_count" : 3,
                    "brand_avg_price" : {
                      "value" : 1666.6666666666667
                    }
                  },
                  {
                    "key" : "三星",
                    "doc_count" : 1,
                    "brand_avg_price" : {
                      "value" : 8000.0
                    }
                  }
                ]
              }
            },
            {
              "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
                    }
                  }
                ]
              }
            },
            {
              "key" : "蓝色",
              "doc_count" : 2,
              "color_avg_price" : {
                "value" : 2000.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" : 1500.0
                    }
                  },
                  {
                    "key" : "小米",
                    "doc_count" : 1,
                    "brand_avg_price" : {
                      "value" : 2500.0
                    }
                  }
                ]
              }
            }
          ]
        }
      }
    }

3.4 metric:统计极值

我们现在需要统计每种颜色的电视机的最高价和最低价:

    GET /tvs/_search
    {
       "size" : 0,
       "aggs": {
          "colors": {
             "terms": {
                "field": "color"
             },
             "aggs": {
                "min_price" : { "min": { "field": "price"} }, 
                "max_price" : { "max": { "field": "price"} }
             }
          }
       }
    }

返回结果就不贴了,通过上述的几个示例我们可以看到,90%的常见数据分析操作,无非就是count、avg、max、min、sum之类的metric操作。

四、总结

本章,我介绍了Elasticsearch中的聚合分析操作,聚合分析的核心就是先分组,然后对组内的记录执行metric操作,也可以分组之后再细粒度分组。常用的metric操作无非就是些求总数、平均值、极值之类的操作。

阅读全文
  • 点赞