Elasticsearch 聚合套路

聚合套路

类似于 DSL 查询表达式,聚合也有可组合的语法:独立单元的功能可以被混合起来提供你需要的自定义行为。这意味着只需要学习很少的基本概念,就可以得到几乎无尽的组合。

先说ES聚合的两个基本概念:桶(Buckets)和指标(Metrics)。

  • 桶(Buckets):满足特定条件的文档的集合。分组的意思,类似SQL中的GROUP BY
  • 指标(Metrics):对桶内的文档进行统计计算。注意理解黄色部分,类似于SQL中的COUNT() 、 SUM() 、 MAX() 等统计方法。

注意记住并理解上面引用文字中标黄的那一段。它就是聚合套路的核心点

开始下面的内容之间,我们先去官网‘借’点数据。

POST /cars/transactions/_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" }

terms(分组、group by)

我们先来个最简单的。看下有多少种颜色的汽车。

GET cars/transactions/_search
{
  "size": 0,
  "aggs": {
    "取一个你想要的名字": {
      "terms": {
        "field": "color.keyword",
        "size": 10
      }
    }
  }
}

  • 聚合操作被置于顶层参数 aggs 之下(全称aggregations)
  • 为你的取一个你想要的名字,以便在应用里面解析结果
  • 最外面的size设置成 0,因为我们并不关心搜索结果的具体内容,所以将返回记录数设置为 0 来提高查询速度。
  • terms里面的size,用来限制统计结果数量。配合order参数,可以很轻松完成TopN的需求

sum(求和)

我们现在需要统计汽车的总销售额。

GET cars/transactions/_search
{
  "size": 0,
  "aggs": {
    "取一个你想要的名字": {
      "sum": {
        "field": "price"
      }
    }
  }
}

组合聚合

在我们组合在我们使用不同的嵌套方案时,聚合的力量才能真正得以显现。

将度量嵌套于桶内

标题看起来太官方了,翻译过来就是在分组之后统计某些数量。我们现在需要统计每种颜色汽车的销售额,并按销售额降序排列

GET cars/transactions/_search
{
  "size": 0,
  "aggs": {
    "取一个你想要的名字1": {
      "terms": {
        "field": "color.keyword",
        "order": {
          "取一个你想要的名字2": "desc"
        }
      },
      "aggs": {
        "取一个你想要的名字2": {
          "sum": {
            "field": "price"
          }
        }
      }
    }
  }
}
  • 我们为求和添加 aggs 层。通过新的aggs层来嵌套不同的聚合方案。我喜欢叫它子聚合
  • 为求和取一个你想要的名字2,方便解析
  • 使用terms order 参数,根据求和结果倒序。

嵌套桶

在我们使用不同的嵌套方案时,聚合的力量才能真正得以显现。在前例中,我们已经看到如何将一个度量嵌入桶中,它的功能已经十分强大了。但真正令人激动的分析来自于将桶嵌套进 另外一个桶 所能得到的结果。

现在我们想知道每个颜色汽车的制造商

GET cars/transactions/_search
{
  "size": 0,
  "aggs": {
    "取一个你想要的名字1": {
      "terms": {
        "field": "color.keyword",
        "order": {
          "取一个你想要的名字2": "desc"
        },
        "size": 10
      },
      "aggs": {
        "取一个你想要的名字2": {
          "sum": {
            "field": "price"
          }
        },
        "取一个你想要的名字3":{
          "terms": {
            "field": "make.keyword",
            "size": 10
          }
        }
      }
    }
  }
}

我们在上一个例子的基础上,又添加一个对于make.keyword字段进行分组的桶,名叫取一个你想要的名字3,它位于取一个你想要的名字1的子聚合里同取一个你想要的名字2平级。

再嵌套桶

现在我们计算每种颜色汽车的生产商生产的对应颜色的汽车的最高和最低价。

GET cars/transactions/_search
{
  "size": 0,
  "aggs": {
    "groupByColor": {
      "terms": {
        "field": "color.keyword",
        "order": {
          "sumPrice": "desc"
        },
        "size": 10
      },
      "aggs": {
        "sumPrice": {
          "sum": {
            "field": "price"
          }
        },
        "groupByMake": {
          "terms": {
            "field": "make.keyword",
            "size": 10
          },
          "aggs": {
            "max_price": {
              "max": {
                "field": "price"
              }
            },
            "min_price": {
              "min": {
                "field": "price"
              }
            }
          }
        }
      }
    }
  }
}

仔细找一下,我们在上一个例子的生产商分组桶(groupByMake)下又添加了两个子聚合分别是max_pricemin_price

其实看到这里已经可以总结聚合的套路了:==记住聚合关键字,根据业务对不同的聚合方案进行嵌套==

histogram(直方图)

ES的聚合能够十分容易地将它们转换成图表和图形。histogram能够直接将某些字段转成直方图。
比如我们以10000为区间,将汽车销售额转为直方图:

GET cars/transactions/_search
{
  "size": 0,
  "aggs": {
    "histogram": {
      "histogram": {
        "field": "price",
        "interval": 10000
      }
    }
  }
}

我们在histogram里面嵌套sum聚合还可以得到每个区间的总销售额:

GET cars/transactions/_search
{
  "size": 0,
  "aggs": {
    "histogram": {
      "histogram": {
        "field": "price",
        "interval": 10000
      },
      "aggs": {
        "sum": {
          "sum": {
            "field": "price"
          }
        }
      }
    }
  }
}

需要注意的是,只支持数值类型的字段。

date_histogram(日期直方图)

顾名思义,这个聚合是专门用来处理时间的。因为就平时业务来看,时间的直方图很常见,但是histogram无法自动识别日期,所以便有了date_histogram的出现。现在我们先来按月生成一个直方图:

GET cars/transactions/_search
{
  "size": 0,
  "aggs": {
    "histogram": {
      "date_histogram": {
        "field": "sold",
        "interval": "month"
      }
    }
  }
}
部分返回结果:
"histogram": {
      "buckets": [
        {
          "key_as_string": "2014-01-01T00:00:00.000Z",
          "key": 1388534400000,
          "doc_count": 1
        }
      ]
    }

我们先看下date_histogram的interval,它是一个日历术语,它有以下几个值:

  • year:年
  • month:月
  • quarter:季
  • week:周
  • day:日
  • hour:时
  • minute:分
  • second:秒

参数:format

接着我们看返回结果的key是一个时间戳,key_as_string 则是一个非常标准的时间格式,但是他们可能跟我们需要的不一样,于是我们可以通过date_histogram的==format==参数来格式化时间:

GET cars/transactions/_search
{
  "size": 0,
  "aggs": {
    "histogram": {
      "date_histogram": {
        "field": "sold",
        "interval": "month",
        "format": "yyyy-MM-dd"
      }
    }
  }
}
返回结果部分:
"histogram": {
      "buckets": [
        {
          "key_as_string": "2014-01-01",
          "key": 1388534400000,
          "doc_count": 1
        }
      ]
    }

此时我们再看key_as_string,已经不需要解释什么。

参数:min_doc_count

min_doc_count 可以控制 doc_count 最小从多少开始返回。默认是不会返回 doc_count 为0结果。

很多时候,我们想直接拿到可以用的结果,而不需要在应用中再做一些后期处理,此时我们便可以把 min_doc_count 设置为 0 :

GET cars/transactions/_search
{
  "size": 0,
  "aggs": {
    "histogram": {
      "date_histogram": {
        "field": "sold",
        "interval": "month",
        "format": "yyyy-MM-dd",
        "min_doc_count": 0
      }
    }
  }
}

我们执行以上语句,会发现好几个月 doc_count 为0的结果也有返回了。但是还有一个问题,只有一到十一月的数据,但是我们其实是想要一整年的数据,此时我们需要借助另外一个参数:extended_bounds

参数:extended_bounds

该参数用来限制数据的范围,因为ES默认统计field最大值和最小值之间的所有数据。如上一个例子,数据中sold字段的最大值是 2014-11-05 ,最小值是 2014-01-01 ,所以我们并没有取到整年的数据。现在我们用 extended_bounds 参数来让ES返回一整年:

GET cars/transactions/_search
{
  "size": 0,
  "aggs": {
    "histogram": {
      "date_histogram": {
        "field": "sold",
        "interval": "month",
        "format": "yyyy-MM-dd",
        "min_doc_count": 0,
        "extended_bounds": {
          "min": "2014-01-01",
          "max": "2014-12-31"
        }
      }
    }
  }
}

此时我们执行以上语句,便可以返回2014年每个月的数据了。

cumulative_sum(累加)

我们现在需要统计每月销售额以及每月累计销售额。

GET cars/transactions/_search
{
  "size": 0,
  "aggs": {
    "histogram": {
      "date_histogram": {
        "field": "sold",
        "interval": "month",
        "format": "yyyy-MM-dd",
        "min_doc_count": 0
      },
      "aggs": {
        "sumPath": {
          "sum": {
            "field": "price"
          }
        },
        "cumulative_sum": {
          "cumulative_sum": {
            "buckets_path": "sumPath"
          }
        }
      }
    }
  }
}

需要注意的是 cumulative_sum 只能在 histogram 和 date_histogram 下使用,并且min_doc_count参数必须设置为0。 buckets_path 为需要累积和的桶的路径,且路径指向的目标必须是数字

在聚合中使用 script

我们有两个字段field1和field2。如果我们需要对 field1 和 field2 之和求和要怎么处理?其中一种方案是增加一个字段用户存放field1和field2之和,然后重置index。但是过程很麻烦且不灵活。假设又多了一个字段 field3 ,要对 field1、field2、field3之和求和?

对上述问题我们便可以采用 script 参数进行处理。例如:

"total": {
    "sum": {
        "script": "doc['file1'].value + doc['field2'].value"
    }
}

多桶排序

内置排序

一个聚合桶默认按照 doc_count 值的升序排序。在聚合中有个order参数(前面的例子中有用到过),它允许我们可以根据以下几个值中的一个值进行排序:

  • _count:按文档数排序。对 terms 、 histogram 、 date_histogram 有效。
  • _term :按词项的字符串值的字母顺序排序。只在 terms 内使用。
  • _key:按每个桶的键值数值排序(理论上与 _term 类似)。 只在 histogram 和 date_histogram 内使用。

按度量排序

大多我们会想基于度量计算的结果值进行排序。我们可以增加一个度量,再指定 order 参数引用这个度量即可。
如在我们的汽车销售分析中,我们可能想按照汽车颜色创建一个销售条状图表,但按照汽车平均售价的升序进行排序:

GET /cars/transactions/_search
{
  "size": 0,
  "aggs": {
    "colors": {
      "terms": {
        "field": "color.keyword",
        "order": {
          "avg_price": "asc"
        }
      },
      "aggs": {
        "avg_price": {
          "avg": {
            "field": "price"
          }
        }
      }
    }
  }
}

多桶排序

order 参数可以接受一个桶的路径,在根据路径进行排序。我们可以定义更深的路径,将度量用尖括号( > )嵌套起来,像这样:

my_bucket>another_bucket>metric

filter

filter 是一种特殊的桶(过滤桶)。我们可以指定一个过滤桶,当文档满足过滤桶的条件时,我们将其加入到桶内。
用汽车销售举个例子,统计出销售数据中一月到三月的销售额:

GET /cars/transactions/_search
{
  "size": 0,
  "aggs": {
    "my_filter": {
      "filter": {
        "range": {
          "sold": {
            "gte": "2014-01-01",
            "lte": "2014-03-31"
          }
        }
      },
      "aggs": {
        "sum": {
          "sum": {
            "field": "price"
          }
        }
      }
    }
  }
}

给聚合加上查询条件

大多数具体的业务中,很少会有这么干瘪瘪直接聚合的,都会跟上各种查询条件。此时我们便可以把query条件加上,对整个聚合做条件限制。

用汽车销售举个例子,我们现在需要统计toyota生产商生成了哪些颜色的汽车:

GET /cars/transactions/_search
{
  "size": 0,
  "query": {
    "term": {
      "make.keyword": {
        "value": "toyota"
      }
    }
  }, 
  "aggs": {
    "terms": {
      "terms": {
        "field": "color.keyword",
        "size": 10
      }
    }
  }
}

是不是感觉功能和 filter 有些类似?他们功能其实是差不多,但是作用范围不同:

  • query 对整个聚合做条件限制,对整个聚合生效。
  • filter 对某个桶作过滤,对该桶的子桶才会生效。

转载于:https://my.oschina.net/tianyuliang/blog/2209004

你可能感兴趣的:(Elasticsearch 聚合套路)