你的位置:首页 > 信息动态 > 新闻中心
信息动态
联系我们

深耕ElasticSearch - 尝试聚合

2021/12/28 1:10:49

文章目录

    • 1. 高阶概念
    • 2. 尝试聚合
    • 3. 添加度量指标
    • 4. 嵌套桶
    • 5. 最后的修改

1. 高阶概念

桶(Buckets):满足特定条件的文档的集合

指标(Metrics):对桶内的文档进行统计计算

这就是全部了!每个聚合都是一个或者多个桶和零个或者多个指标的组合。翻译成粗略的SQL语句来解释吧:

SELECT COUNT(color)  // COUNT(color) 相当于指标
FROM table
GROUP BY color      // GROUP BY color 相当于桶。

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

1. 桶的概念

桶简单来说就是满足特定条件的文档的集合:

  • 一个雇员属于 男性 桶或者 女性
  • 奥尔巴尼属于 纽约
  • 日期2014-10-28属于 十月

当聚合开始被执行,每个文档里面的值通过计算来决定符合哪个桶的条件。如果匹配到,文档将放入相应的桶并接着进行聚合操作。

桶也可以被嵌套在其他桶里面,提供层次化的或者有条件的划分方案。例如,辛辛那提会被放入俄亥俄州这个桶,而整个俄亥俄州桶会被放入美国这个桶。

Elasticsearch 有很多种类型的桶,能让你通过很多种方式来划分文档(时间、最受欢迎的词、年龄区间、地理位置等等)。其实根本上都是通过同样的原理进行操作:基于条件来划分文档。

2. 指标

桶能让我们划分文档到有意义的集合,但是最终我们需要的是对这些桶内的文档进行一些指标的计算。分桶是一种达到目的的手段:它提供了一种给文档分组的方法来让我们可以计算感兴趣的指标。

大多数指标是简单的数学运算(例如最小值、平均值、最大值,还有汇总),这些是通过文档的值来计算。在实践中,指标能让你计算像平均薪资、最高出售价格、95%的查询延迟这样的数据。

3. 桶和指标的组合

聚合是由桶和指标组成的。 聚合可能只有一个桶,可能只有一个指标,或者可能两个都有。也有可能有一些桶嵌套在其他桶里面。

4. 举例

city name
---------- 
北京 小李
---------- 
北京 小王
---------- 
上海 小张
----------
上海 小丽
---------- 
上海 小陈

基于city划分buckets:按照某个字段进行bucket划分,字段的值相同的那些数据,就会被划分到一个bucket中,划分出来两个bucket,一个是北京bucket,一个是上海bucket

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

有一些mysql的sql知识的话,聚合,首先第一步就是分组,对每个组内的数据进行聚合分析,分组,就是我们的bucket

当我们有了一堆bucket之后,就可以对每个bucket中的数据进行聚合分词了,比如说计算一个bucket内所有数据的数量,或者计算一个bucket内所有数据的平均值,最大值,最小值。metric就是对一个bucket执行的某种聚合分析的操作,比如说求平均值,求最大值,求最小值

select count(*)
from access_log
group by user_id

bucket:group by user_id : 那些user_id相同的数据,就会被划分到一个bucket中
metric:count(*):对每个user_id bucket中所有的数据,计算一个数量

2. 尝试聚合

1、创建索引映射mapping :

PUT /cars 
{
  "mappings": {
    "properties": {
      "price":{
        "type": "integer"
      },
      "color":{
        "type": "keyword"
      },
      "make":{
        "type": "keyword"
      },
      "sold":{
        "type": "date"
      }
    }
  }
}

2、索引文档:

POST /cars/_bulk
{ "index": {}}
{ "price" : 10000, "color" : "red", "make" : "honda", "sold" : "2014-10-28" }
{ "index": {}}
{ "price" : 20000, "color" : "red", "make" : "honda", "sold" : "2014-11-05" }
{ "index": {}}
{ "price" : 30000, "color" : "green", "make" : "ford", "sold" : "2014-05-18" }
{ "index": {}}
{ "price" : 15000, "color" : "blue", "make" : "toyota", "sold" : "2014-07-02" }
{ "index": {}}
{ "price" : 12000, "color" : "green", "make" : "toyota", "sold" : "2014-08-19" }
{ "index": {}}
{ "price" : 20000, "color" : "red", "make" : "honda", "sold" : "2014-11-05" }
{ "index": {}}
{ "price" : 80000, "color" : "red", "make" : "bmw", "sold" : "2014-01-01" }
{ "index": {}}
{ "price" : 25000, "color" : "blue", "make" : "ford", "sold" : "2014-02-12" }

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

GET /cars/_search
{
  "size": 0,  
  "aggs": {               // 聚合操作被置于顶层参数aggs之下(aggregations 同样有效)
    "popular_color": {    // 为聚合指定一个我们想要名称
      "terms": {          // 定义单个桶的类型 terms
        "field": "color",
        "size": 10        // 只显示聚合结果的10条数据
      }
    }
  }
}

可能会注意到我们将 size 设置成 0 。我们并不关心搜索结果的具体内容,所以将返回记录数设置为 0 来提高查询速度。

然后我们为聚合定义一个名字,名字的选择取决于使用者,响应的结果会以我们定义的名字为标签,这样应用就可以解析得到的结果。

随后我们定义聚合本身,在本例中,我们定义了一个单 terms 桶。 这个 terms 桶会为每个碰到的唯一词项动态创建新的桶。 因为我们告诉它使用 color 字段,所以 terms 桶会为每个颜色动态创建新桶。

{
  "hits" : {        // 因为我们设置了 size 参数,所以不会有 hits 搜索结果返回。
    "total" : {
      "value" : 8,
      "relation" : "eq"
    },
    "max_score" : null,
    "hits" : [ ]
  },
  "aggregations" : {
    "popular_color" : {    // popular_colors聚合是作为aggregations字段的一部分被返回的。
      "doc_count_error_upper_bound" : 0,
      "sum_other_doc_count" : 0,
      "buckets" : [
        {
          "key" : "red",   // 每个桶的 `key` 都与 `color` 字段里找到的唯一词对应
          "doc_count" : 4  // 每个桶的数量代表该颜色的文档数量。
        },
        {
          "key" : "blue",
          "doc_count" : 2
        },
        {
          "key" : "green",
          "doc_count" : 2
        }
      ]
    }
  }
}

响应包含多个桶,每个对应一个唯一颜色(例如:红 或 绿)。每个桶也包括 聚合进 该桶的所有文档的数量。例如,有四辆红色的车。

前面的这个例子完全是实时执行的:一旦文档可以被搜到,它就能被聚合。这也就意味着我们可以直接将聚合的结果源源不断的传入图形库,然后生成实时的仪表盘。 不久,你又销售了一辆银色的车,我们的图形就会立即动态更新银色车的统计信息。

3. 添加度量指标

前面的例子告诉我们每个桶里面的文档数量,这很有用。但通常,我们的应用需要提供更复杂的文档度量。 例如,每种颜色汽车的平均价格是多少?

为了获取更多信息,我们需要告诉 Elasticsearch 使用哪个字段,计算何种度量。 这需要将度量嵌套在桶内, 度量会基于桶内的文档计算统计结果。

让我们继续为汽车的例子加入 average 平均度量:每种颜色有几辆汽车,这几辆汽车的平均价格是多少?

GET /cars/_search
{
  "size": 0,
  "aggs": {
    "color_agg": {
      "terms": {
        "field": "color"
      },
      "aggs": { 		     // agg和terms是同一层的,代表是子聚合
        "price_avg_agg": {   // 我们需要给度量起一个名字 
          "avg": {           // 指定度量本身
            "field": "price" // 我们想要计算平均值的字段
          }
        }
      }
    }
  }
}

正如所见,我们用前面的例子加入了新的 aggs 层。这个新的聚合层让我们可以将 avg 度量嵌套置于 terms 桶内。实际上,这就为每个颜色生成了平均价格。

{
  "hits" : {
    "total" : {
      "value" : 8,
      "relation" : "eq"
    },
    "max_score" : null,
    "hits" : [ ]
  },
  "aggregations" : {
    "color_agg" : {
      "doc_count_error_upper_bound" : 0,
      "sum_other_doc_count" : 0,
      "buckets" : [
        {
          "key" : "red",
          "doc_count" : 4,
          "price_avg_agg" : {
            "value" : 32500.0
          }
        },
        {
          "key" : "blue",
          "doc_count" : 2,
          "price_avg_agg" : {
            "value" : 20000.0
          }
        },
        {
          "key" : "green",
          "doc_count" : 2,
          "price_avg_agg" : {
            "value" : 21000.0
          }
        }
      ]
    }
  }
} 

尽管响应只发生很小改变,实际上我们获得的数据是增长了。之前,我们知道有四辆红色的车,现在,红色车的平均价格是 $32,500 美元。这个信息可以直接显示在报表或者图形中。

4. 嵌套桶

在我们使用不同的嵌套方案时,聚合的力量才能真正得以显现。 在前例中,我们已经看到如何将一个度量嵌入桶中,它的功能已经十分强大了。

但真正令人激动的分析来自于将桶嵌套进另外一个桶所能得到的结果。 现在,我们想知道每个颜色的汽车制造商的分布:

GET /cars/_search
{
  "size": 0,
  "aggs": {
    "color_agg": {
      "terms": {
        "field": "color"
      },
      "aggs": {
        "price_avg_agg": {
          "avg": {
            "field": "price"
          }
        },
        "make_agg":{        // 另一个聚合make_agg被加入到了 color 颜色桶中
          "terms": {
            "field": "make" // 这个聚合是 terms 桶,它会为每个汽车制造商生成唯一的桶。
          }
        }
      }
    }
  }
}

这里发生了一些有趣的事。 首先,我们可能会观察到之前例子中的 avg_price 度量完全没有变化,还在原来的位置。 avg_price 度量告诉我们每种颜色汽车的平均价格。它与其他的桶和度量相互独立。

make 聚合是一个 terms 桶(嵌套在 colorsterms 桶内)。这意味着它会为数据集中的每个唯一组合生成( colormake )元组,让我们看看返回的响应:

{
  "aggregations" : {
    "color_agg" : {
      "doc_count_error_upper_bound" : 0,
      "sum_other_doc_count" : 0,
      "buckets" : [
        {
          "key" : "red",
          "doc_count" : 4,
          "price_avg_agg" : {   // 前例中的 avg_price 度量仍然维持不变
            "value" : 32500.0  
          },
          "make_agg" : {        // 正如期望的那样,新的聚合嵌入在每个颜色桶中
            "doc_count_error_upper_bound" : 0,
            "sum_other_doc_count" : 0,
            "buckets" : [
              {
                "key" : "honda", // 按不同制造商分解的每种颜色下车辆信息。
                "doc_count" : 3
              },
              {
                "key" : "bmw",
                "doc_count" : 1
              }
            ]
          }
        },
        {
          "key" : "blue",
          "doc_count" : 2,
          "price_avg_agg" : {
            "value" : 20000.0
          },
          "make_agg" : {
            "doc_count_error_upper_bound" : 0,
            "sum_other_doc_count" : 0,
            "buckets" : [
              {
                "key" : "ford",
                "doc_count" : 1
              },
              {
                "key" : "toyota",
                "doc_count" : 1
              }
            ]
          }
        },
        {
          "key" : "green",
          "doc_count" : 2,
          "price_avg_agg" : {
            "value" : 21000.0
          },
          "make_agg" : {
            "doc_count_error_upper_bound" : 0,
            "sum_other_doc_count" : 0,
            "buckets" : [
              {
                "key" : "ford",
                "doc_count" : 1
              },
              {
                "key" : "toyota",
                "doc_count" : 1
              }
            ]
          }
        }
      ]
    }
  }
}

响应结果告诉我们以下几点:

  • 红色车有四辆。
  • 红色车的平均售价是 $32,500 美元。
  • 其中三辆是 Honda 本田制造,一辆是 BMW 宝马制造。

5. 最后的修改

让我们回到话题的原点,在进入新话题之前,对我们的示例做最后一个修改, 为每个汽车生成商计算最低和最高的价格:

GET /cars/_search
{
  "size": 0,
  "aggs": {
    "color_agg": {
      "terms": {
        "field": "color"
      },
      "aggs": {
        "price_avg_agg": {
          "avg": {
            "field": "price"
          }
        },
        "make_agg":{
          "terms": {
            "field": "make"
          },
          "aggs":{
            "max_price_agg":{
              "max": {
                "field": "price"
              }
            },
            "min_price_agg":{
              "min": {
                "field": "price"
              }
            }
          }
        }
      }
    }
  }
}

输出结果为:

{
  "aggregations" : {
    "color_agg" : {
      "doc_count_error_upper_bound" : 0,
      "sum_other_doc_count" : 0,
      "buckets" : [
        {
          "key" : "red",
          "doc_count" : 4,
          "price_avg_agg" : {
            "value" : 32500.0
          },
          "make_agg" : {
            "doc_count_error_upper_bound" : 0,
            "sum_other_doc_count" : 0,
            "buckets" : [
              {
                "key" : "honda",
                "doc_count" : 3,
                "min_price_agg" : {
                  "value" : 10000.0
                },
                "max_price_agg" : {
                  "value" : 20000.0
                }
              },
              {
                "key" : "bmw",
                "doc_count" : 1,
                "min_price_agg" : {
                  "value" : 80000.0
                },
                "max_price_agg" : {
                  "value" : 80000.0
                }
              }
            ]
          }
        },
        {
          "key" : "blue",
          "doc_count" : 2,
          "price_avg_agg" : {
            "value" : 20000.0
          },
          "make_agg" : {
            "doc_count_error_upper_bound" : 0,
            "sum_other_doc_count" : 0,
            "buckets" : [
              {
                "key" : "ford",
                "doc_count" : 1,
                "min_price_agg" : {
                  "value" : 25000.0
                },
                "max_price_agg" : {
                  "value" : 25000.0
                }
              },
              {
                "key" : "toyota",
                "doc_count" : 1,
                "min_price_agg" : {
                  "value" : 15000.0
                },
                "max_price_agg" : {
                  "value" : 15000.0
                }
              }
            ]
          }
        },
        {
          "key" : "green",
          "doc_count" : 2,
          "price_avg_agg" : {
            "value" : 21000.0
          },
          "make_agg" : {
            "doc_count_error_upper_bound" : 0,
            "sum_other_doc_count" : 0,
            "buckets" : [
              {
                "key" : "ford",
                "doc_count" : 1,
                "min_price_agg" : {
                  "value" : 30000.0
                },
                "max_price_agg" : {
                  "value" : 30000.0
                }
              },
              {
                "key" : "toyota",
                "doc_count" : 1,
                "min_price_agg" : {
                  "value" : 12000.0
                },
                "max_price_agg" : {
                  "value" : 12000.0
                }
              }
            ]
          }
        }
      ]
    }
  }
}

有了这两个桶,我们可以对查询的结果进行扩展并得到以下信息:

  • 有四辆红色车。
  • 红色车的平均售价是 $32,500 美元。
  • 其中三辆红色车是 Honda 本田制造,一辆是 BMW 宝马制造。
  • 最便宜的红色本田售价为 $10,000 美元。
  • 最贵的红色本田售价为 $20,000 美元。