1. 聚合(aggregations)

聚合可以让我们极其方便的实现对数据的统计、分析。例如:

  • 什么品牌的手机最受欢迎?
  • 这些手机的平均价格、最高价格、最低价格?
  • 这些手机每月的销售情况如何?

实现这些统计功能的比数据库的sql要方便的多,而且查询速度非常快,可以实现实时搜索效果。

1.1 基本概念

Elasticsearch中的聚合,包含多种类型,最常用的两种,一个叫,一个叫度量

桶(bucket)

桶的作用,是按照某种方式对数据进行分组,每一组数据在ES中称为一个,例如我们根据国籍对人划分,可以得到中国桶英国桶日本桶……或者我们按照年龄段对人进行划分:0~10,10~20,20~30,30~40等。

Elasticsearch中提供的划分桶的方式有很多:

  • Date Histogram Aggregation:根据日期阶梯分组,例如给定阶梯为周,会自动每周分为一组
  • Histogram Aggregation:根据数值阶梯分组,与日期类似
  • Terms Aggregation:根据词条内容分组,词条内容完全匹配的为一组
  • Range Aggregation:数值和日期的范围分组,指定开始和结束,然后按段分组
  • ……

bucket aggregations 只负责对数据进行分组,并不进行计算,因此往往bucket中往往会嵌套另一种聚合:metrics aggregations即度量

度量(metrics)

分组完成以后,我们一般会对组中的数据进行聚合运算,例如求平均值、最大、最小、求和等,这些在ES中称为度量

比较常用的一些度量聚合方式:

  • Avg Aggregation:求平均值
  • Max Aggregation:求最大值
  • Min Aggregation:求最小值
  • Percentiles Aggregation:求百分比
  • Stats Aggregation:同时返回avg、max、min、sum、count等
  • Sum Aggregation:求和
  • Top hits Aggregation:求前几
  • Value Count Aggregation:求总数
  • ……

1.2 聚合为桶

首先,我们按照手机的品牌attr.brand.keyword来划分

1
2
3
4
5
6
7
8
9
10
11
GET /mall/_search
{
"size" : 0,
"aggs" : {
"brands" : {
"terms" : {
"field" : "attr.brand.keyword"
}
}
}
}
  • size: 查询条数,这里设置为0,因为我们不关心搜索到的数据,只关心聚合结果,提高效率
  • aggs:声明这是一个聚合查询,是aggregations的缩写
    • brands:给这次聚合起一个名字,任意。
      • terms:划分桶的方式,这里是根据词条划分
        • field:划分桶的字段

结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
{
"took" : 124,
"timed_out" : false,
"_shards" : {
"total" : 2,
"successful" : 2,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : 10,
"max_score" : 0.0,
"hits" : [ ]
},
"aggregations" : {
"brands" : {
"doc_count_error_upper_bound" : 0,
"sum_other_doc_count" : 0,
"buckets" : [
{
"key" : "华为",
"doc_count" : 4
},
{
"key" : "小米",
"doc_count" : 4
},
{
"key" : "oppo",
"doc_count" : 1
},
{
"key" : "vivo",
"doc_count" : 1
}
]
}
}
}
  • hits:查询结果为空,因为我们设置了size为0
  • aggregations:聚合的结果
  • brands:我们定义的聚合名称
  • buckets:查找到的桶,每个不同的品牌字段值都会形成一个桶
    • key:这个桶对应的品牌字段的值
    • doc_count:这个桶中的文档数量

1.3 桶内度量

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

因此,我们需要告诉Elasticsearch使用哪个字段使用何种度量方式进行运算,这些信息要嵌套在内,度量的运算会基于内的文档进行

现在,我们为刚刚的聚合结果添加 求价格平均值的度量:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
GET /mall/_search
{
"size" : 0,
"aggs" : {
"brands" : {
"terms" : {
"field" : "attr.brand.keyword"
},
"aggs":{
"avg_price": {
"avg": {
"field": "price"
}
}
}
}
}
}
  • aggs:我们在上一个aggs(brands)中添加新的aggs。可见度量也是一个聚合
  • avg_price:聚合的名称
  • avg:度量的类型,这里是求平均值
  • field:度量运算的字段
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
{
"took" : 41,
"timed_out" : false,
"_shards" : {
"total" : 2,
"successful" : 2,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : 10,
"max_score" : 0.0,
"hits" : [ ]
},
"aggregations" : {
"brands" : {
"doc_count_error_upper_bound" : 0,
"sum_other_doc_count" : 0,
"buckets" : [
{
"key" : "华为",
"doc_count" : 4,
"avg_price" : {
"value" : 3999.0
}
},
{
"key" : "小米",
"doc_count" : 4,
"avg_price" : {
"value" : 3499.0
}
},
{
"key" : "oppo",
"doc_count" : 1,
"avg_price" : {
"value" : 2799.0
}
},
{
"key" : "vivo",
"doc_count" : 1,
"avg_price" : {
"value" : 2699.0
}
}
]
}
}
}

可以看到每个桶中都有自己的avg_price字段,这是度量聚合的结果

1.4 桶内嵌套桶

刚刚的案例中,我们在桶内嵌套度量运算。事实上桶不仅可以嵌套运算, 还可以再嵌套其它桶。也就是说在每个分组中,再分更多组。

比如:我们想统计每个品牌都生产了那些产品,按照attr.category.keyword字段再进行分桶

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
GET /mall/_search
{
"size" : 0,
"aggs" : {
"brands" : {
"terms" : {
"field" : "attr.brand.keyword"
},
"aggs":{
"avg_price": {
"avg": {
"field": "price"
}
},
"categorys": {
"terms": {
"field": "attr.category.keyword"
}
}
}
}
}
}

部分结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
{
"took" : 19,
"timed_out" : false,
"_shards" : {
"total" : 2,
"successful" : 2,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : 10,
"max_score" : 0.0,
"hits" : [ ]
},
"aggregations" : {
"brands" : {
"doc_count_error_upper_bound" : 0,
"sum_other_doc_count" : 0,
"buckets" : [
{
"key" : "华为",
"doc_count" : 4,
"categorys" : {
"doc_count_error_upper_bound" : 0,
"sum_other_doc_count" : 0,
"buckets" : [
{
"key" : "手机",
"doc_count" : 3
},
{
"key" : "笔记本",
"doc_count" : 1
}
]
},
"avg_price" : {
"value" : 3999.0
}
},
{
"key" : "小米",
"doc_count" : 4,
"categorys" : {
"doc_count_error_upper_bound" : 0,
"sum_other_doc_count" : 0,
"buckets" : [
{
"key" : "手机",
"doc_count" : 2
},
{
"key" : "电视",
"doc_count" : 1
},
{
"key" : "笔记本",
"doc_count" : 1
}
]
},
"avg_price" : {
"value" : 3499.0
}
},
{
"key" : "oppo",
"doc_count" : 1,
"categorys" : {
"doc_count_error_upper_bound" : 0,
"sum_other_doc_count" : 0,
"buckets" : [
{
"key" : "手机",
"doc_count" : 1
}
]
},
"avg_price" : {
"value" : 2799.0
}
},
{
"key" : "vivo",
"doc_count" : 1,
"categorys" : {
"doc_count_error_upper_bound" : 0,
"sum_other_doc_count" : 0,
"buckets" : [
{
"key" : "手机",
"doc_count" : 1
}
]
},
"avg_price" : {
"value" : 2699.0
}
}
]
}
}
}

  • 我们可以看到,新的聚合categorys被嵌套在原来每一个brands的桶中。
  • 每个品牌下面都根据 attr.category.keyword字段进行了分组
  • 我们能读取到的信息:
    • 华为有4中产品
    • 华为产品的平均售价是 3999.0美元。
    • 其中3种手机产品,1种笔记本产品