一、简介
上文我们说到了聚合的三类聚合,其中包括指标聚合。
Metric aggregations:指标聚合是根据字段值计算量度(如总和或平均值)的量度聚合。
此系列中的聚合基于以某种方式从正在聚合的文档中提取的值来计算度量。这些值通常从文档的字段中提取(使用字段数据),但也可以使用脚本生成。
数值指标聚合是一种特殊类型的指标聚合,用于输出数值。一些聚合输出单个数值指标(例如 avg),称为单值数值指标聚合,其他聚合生成多个指标(例如统计数据),称为多值数值指标聚合。当单值和多值数值指标聚合用作某些存储桶聚合的直接子聚合时,这些聚合之间的区别将发挥作用(某些存储桶聚合使您能够根据每个存储桶中的数值指标对返回的存储桶进行排序),其实就是说指标聚合经常和桶聚合一起使用,就是分组之后求最大,平均之类的。类似于select count(1) from table group by name这样的一个逻辑。
我们本文就来具体来看一下如何使用指标聚合。
二、指标聚合
指标聚合里面有很多类型的指标,包括平均值,最大值,最小值,求和等等二十多种类型,我们就一一来看一下,在操作中体会他的使用。
1、Avg aggregation:均值聚合
一种单值度量聚合,用于计算从聚合文档中提取的数值的平均值。可以从文档中的特定数字或直方图字段中提取这些值。
1.1、聚合检索
我们的衣物索引中有20个衣服的信息,分别有各自的价格,我们可以使用均值聚合来求取它的价格平均值。
# 求取衣服售价的平均值
POST clothes/_search
{
"aggs": {
"avg_clothes_price": {
"avg": {
"field": "price"
}
}
}
}
我们看到它的语法形式就是把聚合逻辑放在aggs块下面,其中你可以为你的指标结果取个名字,我这里的名字是avg_clothes_price(类似于select avg(price) as avg_clothes_price).然后我们选择avg聚合器来对字段price来求取聚合结果。于是我们就得到了,
{
"took": 1,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 20,
"relation": "eq"
},
"max_score": 1,
"hits": [
{
"_index": "clothes",
"_id": "1",
"_score": 1,
"_source": {
"category": "T-shirt",
"name": "纯棉T恤",
"price": 19.99,
"brand": "品牌A",
"desc": "基础款纯棉T恤,适合日常穿着。",
"place_of_origin": "中国"
}
},
{
"_index": "clothes",
"_id": "2",
"_score": 1,
"_source": {
"category": "Jeans",
"name": "修身牛仔裤",
"price": 49.99,
"brand": "品牌B",
"desc": "耐穿的牛仔裤,修身款式。",
"place_of_origin": "越南"
}
},
{
"_index": "clothes",
"_id": "3",
"_score": 1,
"_source": {
"category": "Dress",
"name": "晚礼服",
"price": 89.99,
"brand": "品牌C",
"desc": "适合特殊场合的优雅晚礼服。",
"place_of_origin": "意大利"
}
},
{
"_index": "clothes",
"_id": "4",
"_score": 1,
"_source": {
"category": "Jacket",
"name": "皮夹克",
"price": 129.99,
"brand": "品牌D",
"desc": "时尚的男士皮夹克。",
"place_of_origin": "美国"
}
},
{
"_index": "clothes",
"_id": "5",
"_score": 1,
"_source": {
"category": "Sweater",
"name": "羊毛衫",
"price": 39.99,
"brand": "品牌E",
"desc": "适合冬季的保暖羊毛衫。",
"place_of_origin": "澳大利亚"
}
},
{
"_index": "clothes",
"_id": "6",
"_score": 1,
"_source": {
"category": "Skirt",
"name": "铅笔裙",
"price": 29.99,
"brand": "品牌F",
"desc": "适合办公室穿着的经典铅笔裙。",
"place_of_origin": "英国"
}
},
{
"_index": "clothes",
"_id": "7",
"_score": 1,
"_source": {
"category": "Shorts",
"name": "休闲短裤",
"price": 14.99,
"brand": "品牌G",
"desc": "适合夏天的舒适休闲短裤。",
"place_of_origin": "中国"
}
},
{
"_index": "clothes",
"_id": "8",
"_score": 1,
"_source": {
"category": "Blouse",
"name": "丝绸衬衫",
"price": 59.99,
"brand": "品牌H",
"desc": "柔软的丝绸衬衫,适合女性。",
"place_of_origin": "法国"
}
},
{
"_index": "clothes",
"_id": "9",
"_score": 1,
"_source": {
"category": "Coat",
"name": "冬季大衣",
"price": 199.99,
"brand": "品牌I",
"desc": "适合寒冷天气的厚冬季大衣。",
"place_of_origin": "加拿大"
}
},
{
"_index": "clothes",
"_id": "10",
"_score": 1,
"_source": {
"category": "Socks",
"name": "棉袜",
"price": 4.99,
"brand": "品牌J",
"desc": "一包舒适的棉袜。",
"place_of_origin": "中国"
}
}
]
},
"aggregations": {
"avg_clothes_price": {
"value": 59.39
}
}
}
最后输出我们的avg_clothes_price为59.39。但是你可以看到他面把文档数据也输出了,我们实际上只是想看一个均值,所以我们可以修改一下,不显示文档。
POST clothes/_search
{
"size": 0, # 指定文档返回数为0
"aggs": {
"avg_clothes_price": {
"avg": {
"field": "price"
}
}
}
}
聚合的名称(如上avg_clothes_price)也用作从返回的响应中检索聚合结果的key。
1.2、script 脚本
脚本在es中拥有重要的角色和使用场景,这里我们不多介绍脚本,后面单独开一篇来讲述脚本。
假如此时我想计算的均值非常复杂,不是那么简单。假如此时我们的管理人员想计算一个打8折之后的均值,这样导致我们直接求出来的均值就不对了,那我想弄对怎么办,此时需要在运行时产生一个正确的值然后做取均值。这个很简单,直接使用runtime_mappings来产生运行时字段来做均值。
POST /clothes/_search?size=0
{
"runtime_mappings": {
"discount8.price": {
"type": "double",
"script": {
"source": "emit(doc['price'].value * params.discount)",
"params": {
"discount": 0.8
}
}
}
},
"aggs": {
"avg_clothes_price": {
"avg": {
"field": "discount8.price"
}
}
}
}
你能看到他是制造了一个运行时产生的字段,最后附加在均值聚合器上,得到结果。
1.3、Missing value 缺失值
有的时候你聚合的字段可能是空的,这种字段可能影响你的结果。我们可以使用缺失值来处理。
missing 参数定义应如何处理缺少值的文档。默认情况下,它们将被忽略,但也可以将它们视为具有值。
此时我们再加入一条数据。
PUT clothes/_doc/21
{
"category": "Socks",
"name": "丝袜",
"brand": "品牌T",
"desc": "适合冬季的丝袜神器。",
"place_of_origin": "澳大利亚"
}
你能看到这个数据是没有价格这个字段的。
此时我们再去求均值他是会忽略这个数据,得到的还是59.39这个数字。但是有时候我们这个空值可能是失误了,我们还是想让她有个值,我们就可以使用缺失值,
POST clothes/_search
{
"size": 0,
"aggs": {
"avg_clothes_price": {
"avg": {
"field": "price",
"missing": 7.99
}
}
}
}
价格字段中没有值的文档将与值为 7.99 的文档属于同一存储桶。也就是你给他在求均值的时候赋予了一个默认值。
1.4、Histogram fields 直方图字段
均值也可以用在直方图字段上。
在直方图字段上计算 avg 时,聚合的结果是 values 数组中所有元素的加权平均值,同时考虑了 counts 数组中相同位置的数字。
我们这里模拟一个数据。对于以下索引,该索引存储具有不同网络的延迟指标的预聚合直方图:
PUT metrics_index/_doc/1
{
"network.name" : "net-1",
"latency_histo" : {
"values" : [0.1, 0.2, 0.3, 0.4, 0.5],
"counts" : [3, 7, 23, 12, 6]
}
}
PUT metrics_index/_doc/2
{
"network.name" : "net-2",
"latency_histo" : {
"values" : [0.1, 0.2, 0.3, 0.4, 0.5],
"counts" : [8, 17, 8, 7, 6]
}
}
POST /metrics_index/_search?size=0
{
"aggs": {
"avg_latency":
{ "avg": { "field": "latency_histo" }
}
}
}
我解释一下这个数据values是横坐标,counts是横坐标对应的纵坐标的值。他们是一一对应的。
对于每个 直方图 字段,avg 聚合将 values 数组 <1> 中的每个数字乘以 counts 数组 <2> 中的关联计数相加。所以他是一个加权平均。最终,它将计算所有直方图的这些值的平均值,并返回以下结果:
{
...
"aggregations": {
"avg_latency": {
"value": 0.29690721649
}
}
}
2、Cardinality aggregation:基准聚合
2.1、基准聚合
这个名字看着很奇怪,我们可以把它理解为redis中的HyperLogLog,他起到一个去重之后求总和的作用。比如我们的那个衣服索引。里面的衣服种类有以下10种。
Jeans
Dress
Jacket
Sweater
Skirt
Shorts
Blouse
Coat
Socks
T-shirt
那按照我们的这个理解来说,结果就是10.
POST /clothes/_search?size=0
{
"aggs": {
"type_count": {
"cardinality": {
"field": "category"
}
}
}
}
输出就是10:
"aggregations": {
"type_count": {
"value": 10
}
}
就像redis的HyperLogLog是不准确的一样,他这个去重是存在误差的,但是很低。es中的这个也是不准的。但是他提供了一个阈值参数。precision_threshold
POST /clothes/_search?size=0
{
"aggs": {
"type_count": {
"cardinality": {
"field": "category",
"precision_threshold": 100
}
}
}
}
precision_threshold 选项允许以内存换取准确性,并定义一个唯一计数,低于该计数时,计数应接近准确。高于此值时,计数可能会变得更加模糊。支持的最大值为 40000,高于此数字的阈值将与阈值 40000 具有相同的效果。默认值为 3000。
换言之你可以牺牲一点内存为代价,设置一个值,当计算结果超过这个值的时候你的精确度会进一步降低,但是低于这个值的时候是可以准确的。可见如果不一样的内容太多了,其实误差是存在的,使用的时候需要评估你的误差容忍度。
2.2、script脚本
我们上面的去重都是基于一个字段的,但是如果我想用两个字段组合起来做去重呢,此时可以使用脚本。
POST /clothes/_search?size=0
{
"runtime_mappings": {
"category_and_brand": {
"type": "keyword",
"script": "emit(doc['category'].value + ' ' + doc['brand'].value)"
}
},
"aggs": {
"category_and_brand_count": {
"cardinality": {
"field": "category_and_brand"
}
}
}
}
我们看到他是在运行时产生了一个组合字段category_and_brand,内容是category和brand字段的拼接。
然后我们使用这个拼接字段来进行基准聚合。
3、Stats aggregation:统计聚合
一种多值量度聚合,用于计算从聚合文档中提取的数值的统计信息。如果你不想每次都得求avg,求max,你想一次返回多个指标,那么这里就是你的选择。
返回的统计信息包括:min、max、sum、count 和 avg。
POST /clothes/_search?size=0
{
"aggs": {
"clothes_stats": {
"stats": {
"field": "price"
}
}
}
}
我们看到输出的非常全面了:
"aggregations": {
"clothes_stats": {
"count": 20,
"min": 4.99,
"max": 199.99,
"avg": 59.39,
"sum": 1187.8
}
}
同样可以使用脚本和缺失值的操作来定制你的聚合。
你还可以使用更加全面的统计聚合,extended_stats来输出统计。
此外还有诸多的指标聚合器可以让我们来使用,更加丰富你的业务。我们这里就不一一展开,可以去看看文档。
指标聚合文档