聚合的基础知识

aggregations框架帮助提供基于搜索查询的聚合数据。它基于称为聚合的简单构建块,可以通过组合这些构建块来构建复杂的数据摘要。

我们来对比一下聚合搜索的区别:

//搜索

1
2
3
4
5
6
7
POST /<index_name>/_search
{
"query":
{
... type of query ...
}
}

//聚合

1
2
3
4
5
6
7
8
POST /<index_name>/_search
{
"aggs": {
... type of aggregation ...
},
"query": { ... type of query ... }, //可选部分
"size": 0 //返回结果的数量
}
  • aggs:该元素包含实际聚合查询
  • query:定义聚合的上下文。如果未指定元素,将使用给定索引和类型中的所有文档。如果我们不想将所有数据都考虑用于聚合,而只考虑满足特定条件的某些文档。此查询过滤要提供给实际aggs查询的文档。
  • size:指定在响应中返回多少个命中。默认为 10 ,通常,如果我们只对获取聚合结果感兴趣,我们应该将size 元素设置为0

有许多不同类型的聚合,每种聚合都有自己的目的和输出。为了更好地理解这些类型,通常更容易将它们分为四大类:

  • 存储桶聚合
  • 度量聚合
  • 矩阵聚合
  • 管道聚合

1. 存储桶聚合

构建bucket的聚合家族,其中每个bucket都与一个键和一个文档标准相关联。当执行聚合时,将对上下文中的每个文档计算所有bucket条件,当一个条件匹配时,将认为文档“落在”相关bucket中。在聚合过程的最后,我们将得到一个bucket列表——每个bucket都有一组“属于”它的文档。

Bucket聚合可以出现在聚合查询的顶层或最外层。桶聚合还可以嵌套在其他桶聚合中。

2. Metric aggregations (度量聚合)

在一组文档上跟踪和计算指标的聚合。比如前面我们导入的文档,每条记录都包含了产品的价格。度量聚合可以计算不同的聚合(和、平均值、最小值、最大值等)。

度量聚合可以放在aggregations查询的顶层或最外层。度量聚合也可以嵌套在桶聚合中。度量聚合不能在其中嵌套其他类型的聚合。

3. Matrix(矩阵)聚合

在Elasticsearch 5.0版本中引入了矩阵聚合。矩阵聚合处理多个字段,并在查询上下文中跨所有文档计算矩阵。与度量聚合和桶聚合不同,这个聚合家族还不支持脚本。

矩阵聚合可以嵌套在桶聚合中,但是桶聚合不能嵌套在矩阵聚合中。这仍然是一个相对较新的功能。

4. 管道聚合

管道聚合是可以聚合其他聚合的输出的高阶聚合。这些对于计算一些东西很有用,比如导数。

这是关于Elasticsearch在高层次上支持的不同类型聚合的概述。管道聚合和矩阵聚合相对较新,与度量聚合和桶聚合相比用例更少。

Metric 聚合

本节中,主要讨论以下度量聚合:

  • 和、平均、最小和最大聚合
  • 统计信息和扩展统计信息聚合
  • 基数聚合

1. 和、平均、最小和最大聚合

(1)Sum Aggregation

一个单值度量聚合,它汇总从聚合文档中提取的数值。这些值可以从文档中的特定数字字段提取,也可以由提供的脚本生成。

//从特定文档中汇总 price

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
GET /amazon/_search?size=0
{
"query": {
"constant_score": {
"filter": {
"match": {
"manufacturer.raw": "global software publishing"
}
}
}
},
"aggs": { 1
"global_prices": { 2
"sum": { 3
"field": "price" 4
}
}
}
}
    1. aggs,也可以写成 aggregations ,里面封装聚合查询
    1. 聚合后的名称。
    1. 聚合操作符
    1. 聚合的字段

(2)平均聚合

平均聚合找到特定文档的平均价格:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
GET /amazon/_search
{
"query": {
"constant_score": {
"filter": {
"match": {
"manufacturer.raw": "global software publishing"
}
}
}
},
"aggs": {
"global_prices": {
"avg": {
"field": "price"
}
}
},
"size": 0
}

(3)min 聚合

查找特定文档的字段的最小值。

1
avg 改成  min

(3)max 聚合

查找特定文档的字段的最小值。

1
avg 改成  max

这些聚合非常简单。现在,让我们看看一些更高级的统计信息和扩展。

2. 统计信息和统计信息扩展

这些聚合在一个请求中计算一些公共统计信息,而不需要发出多个请求。这也节省了Elasticsearch方面的资源,因为统计数据是在一次传递中计算的,而不是多次请求。

(1)统计信息

Stats聚合计算一次传递中文档的总数、平均值、最小值、最大值和计数:

1
2
3
4
5
6
7
8
9
10
11
GET /amazon/_search
{
"aggs": {
"global_price": {
"stats": {
"field": "price"
}
}
},
"size": 0
}

//输出如下

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
{
"took" : 2,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 1363,
"relation" : "eq"
},
"max_score" : null,
"hits" : [ ]
},
"aggregations" : {
"global_price" : {
"count" : 1363,
"min" : 0.0,
"max" : 101515.55,
"avg" : 347.0741819515774,
"sum" : 473062.11
}
}
}

可以看到,download_stats元素的响应包含count、min、max、average和sum;所有内容都包含在同一个响应中。这非常方便,因为它减少了多个请求的开销,还简化了客户机代码。

(2)扩展统计聚合

除了stats聚合返回的统计信息外,extended stats聚合还返回更多的统计信息:

1
2
3
4
5
6
7
8
9
10
11
GET /amazon/_search
{
"aggs": {
"global_price": {
"extended_stats": {
"field": "price"
}
}
},
"size": 0
}

//返回

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
...
"aggregations" : {
"global_price" : {
"count" : 1363,
"min" : 0.0,
"max" : 101515.55,
"avg" : 347.0741819515774,
"sum" : 473062.11,
"sum_of_squares" : 1.32410975224953E10,
"variance" : 9594211.208844285,
"std_deviation" : 3097.452373942864,
"std_deviation_bounds" : {
"upper" : 6541.978929837305,
"lower" : -5847.830565934151
}
}
}
}

它还返回平方和、方差、标准差和标准差界限。

3. Cardinality(基数)聚合

计算不同值的近似计数的单值度量聚合。为特定字段查找基数或惟一值的数量是一个非常常见的需求。如果您的网站上有来自不同访问者的点击流,您可能想要找出在给定的一天、一周或一个月内有多少个访问者。

//找出有多个生产商

1
2
3
4
5
6
7
8
9
10
GET /amazon/_search?size=0
{
"aggs": {
"manufacturer": {
"cardinality": {
"field": "manufacturer.raw"
}
}
}
}

//返回结果

1
2
3
4
5
6
7
8
9
{
...
},
"aggregations" : {
"manufacturer" : {
"value" : 350
}
}
}

Bucket 聚合

桶聚合有助于分析整体与部分之间的关系,以便更好地了解数据。它们有助于将数据分割成更小的部分。桶聚合的每一种类型都将数据分割成不同的段或桶。桶聚合是任何分析过程中使用的最常见的聚合类型。

1. 对字符串数据进行分组

有时,我们可能需要根据具有字符串数据类型的字段(通常是Elasticsearch中关键字类型的字段)来提取或分割数据。

(1)Terms 聚合

Terms 聚合可能是最广泛使用的聚合。它有助于根据给定字段的不同值对数据进行分段或分组。

//查询销售书最多得经销商

1
2
3
4
5
6
7
8
9
10
11
GET /amazon/_search?size=0
{
"aggs": {
"bymanufacturer": {
"terms": {
"field": "manufacturer.raw"
}
}
},
"size": 0
}

指定size = 0以防止返回原始搜索结果。在本例中,我们只想要聚合结果,而不是搜索结果。由于我们没有指定任何顶级查询元素,所以它匹配所有文档。

//响应如下

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
{
"took" : 5,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 1363,
"relation" : "eq"
},
"max_score" : null,
"hits" : [ ]
},
"aggregations" : {
"bymanu" : {
"doc_count_error_upper_bound" : 0,
"sum_other_doc_count" : 900,
"buckets" : [
{
"key" : "adobe",
"doc_count" : 87
},
{
"key" : "encore software",
"doc_count" : 76
},
{
"key" : "topics entertainment",
"doc_count" : 73
},
...

由于指定了 size:0 ,所以 hits.hit 数组为空。

//只显示前三名经销商

1
2
3
4
5
6
7
8
9
10
11
12
GET /amazon/_search
{
"aggs": {
"bymanu": {
"terms": {
"field": "manufacturer.raw",
"size": 3
}
}
},
"size": 0
}

[success]注意,这个大小(在术语聚合中指定)与顶层指定的大小不同。在顶层,size参数用于防止任何搜索命中,而术语聚合中使用的size参数表示要返回的术语桶的最大数量。

2. 对数值数据进行分析

另一种常见情况是我们想要的时候基于数字字段将数据分段或切片到各种桶中。例如,我们可能希望按不同的价格范围对产品数据进行切片,例如最高10元,10元到50元,50元到100元等等。

(1)直方图聚合

直方图聚合可以根据一个数值域将数据分割成不同的块。每个片的范围,也称为区间,可以在查询的输入中指定。

1
2
3
4
5
6
7
8
9
10
11
12
POST /amazon/_search?size=0
{
"aggs": {
"by_price": {
"histogram": {
"field": "price",
"interval": 500
}
}
},
"size": 0
}

//返回结果

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
{
"took" : 3,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 1363,
"relation" : "eq"
},
"max_score" : null,
"hits" : [ ]
},
"aggregations" : {
"by_price" : {
"buckets" : [
{
"key" : 0.0,
"doc_count" : 1237
},
{
"key" : 500.0,
"doc_count" : 70
},
{
"key" : 1000.0,
"doc_count" : 12
},
{
"key" : 1500.0,
"doc_count" : 17
},
...

这就是直方图聚合如何使用查询中指定的区间创建相等范围的桶。默认情况下,它包含具有给定间隔的所有桶,而不管该桶中是否有任何文档。可以只返回那些至少包含一些文档的bucket。这可以通过使用min_doc_count参数来实现。如果指定,直方图聚合只返回至少具有指定文档数量的桶。

(2)Range 聚合

如果我们不想让所有的桶都有相同的区间呢?使用范围聚合可以创建大小不等的桶。

范围内from和to都是可选的。如果只指定to,则该bucket包含该bucket中指定值之前的所有文档。to值是互斥的,不包括在当前桶的范围内:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
POST /amazon/_search?size=0
{
"aggs": {
"by_price": {
"range": {
"field": "price",
"ranges": [
{ "to": 10 },
{ "from": 10, "to": 60},
{ "from": 60 }
]
}
}
},
"size": 0
}

//响应如下:

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
{
"took" : 6,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 1363,
"relation" : "eq"
},
"max_score" : null,
"hits" : [ ]
},
"aggregations" : {
"by_price" : {
"buckets" : [
{
"key" : "*-10.0",
"to" : 10.0,
"doc_count" : 293
},
{
"key" : "10.0-60.0",
"from" : 10.0,
"to" : 60.0,
"doc_count" : 578
},
{
"key" : "60.0-*",
"from" : 60.0,
"doc_count" : 492
}
]
}
}
}

//可以为范围桶指定自定义标签

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
POST /amazon/_search?size=0
{
"aggs": {
"by_price": {
"range": {
"field": "price",
"ranges": [
{ "key": "price 0-10" , "to": 10 },
{ "key": "price 10-60" , "from": 10, "to": 60},
{ "from": 60 }
]
}
}
},
"size": 0
}

生成的bucket将使用每个bucket设置键。这有助于在不遍历所有桶的情况下从响应中查找相关桶。

3. 嵌套聚合

当度量标准聚合嵌套在桶聚合内时,将在每个桶中计算度量标准聚合。

//查询价格为 10-20的产品,然后统计这个区段的,各个生产商的产品总和。

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
GET /amazon/_search?size=0
{
"query": {
"bool": {
"must": [

{"range": {
"price": {
"gte": 10,
"lte": 200
}
}}
]
}
},
"aggs": {
"by_manu": {
"terms": {
"field": "manufacturer.raw"
},
"aggs": {
"total_price": {
"sum": {"field": "price"}
}
}
}
}
}

//输出如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
...
"aggregations" : {
"by_manu" : {
"doc_count_error_upper_bound" : 0,
"sum_other_doc_count" : 525,
"buckets" : [
{
"key" : "encore software",
"doc_count" : 59,
"total_price" : {
"value" : 1986.13
}
},
{
"key" : "topics entertainment",
"doc_count" : 57,
"total_price" : {
"value" : 2036.18
}
},
...

//按照降序进行排序

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
GET /amazon/_search?size=0
{
"query": {
"bool": {
"must": [

{"range": {
"price": {
"gte": 10,
"lte": 200
}
}}
]
}
},
"aggs": {
"by_manu": {
"terms": {
"field": "manufacturer.raw",
"order": {
"total_price": "desc"
}
},
"aggs": {
"total_price": {
"sum": {"field": "price"}
}
}
}
}
}

//查找价格范围是10-200的产品,并找出每家生产商每个产品的价格的总和的前三个。

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
GET /amazon/_search?size=0
{
"query": {
"bool": {
"must": [
{"range": {
"price": {
"gte": 10,
"lte": 200
}
}}
]
}
},
"aggs": {
"by_man": {
"terms": {
"field": "manufacturer.raw"
},
"aggs": {
"by_id": {
"terms": {
"field": "id",
"size": 3,
"order": {
"total_price": "desc"
}
},
"aggs": {
"total_price": {
"sum": {"field": "price"}
}
}
}
}
}
}
}

4. 根据自定义条件进行调整

有时,我们可能更想子句控制创建存储桶。以下聚合允许我们根据选择的查询/过滤器创建一个或多个存储桶。

(1)Filter 聚合

Filter聚合允许您使用任意筛选器创建单个bucket,并计算该bucket中的指标

1
2
3
4
5
6
7
8
9
10
11
12
POST /amazon/_search?size=0
{
"aggs": {
"bucket_encore": {
"filter": {
"term": {
"manufacturer.raw": "encore software"
}
}
}
}
}

//返回如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
{
"took" : 1,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 1363,
"relation" : "eq"
},
"max_score" : null,
"hits" : [ ]
},
"aggregations" : {
"bucket_encore" : {
"doc_count" : 76
}
}
}

(2)Filters 聚合

使用筛选器聚合,您可以创建多个bucket,每个bucket都有自己指定的筛选器,这将使满足筛选器要求的文档落入相关的bucket。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
GET /amazon/_search?size=0
{
"aggs": {
"bucket_class": {
"filters": {
"filters": {
"encore": {"match": { "manufacturer.raw": "encore software"}},
"global": {"match": {"manufacturer.raw": "global software publishing"}},
"other": {
"bool": {
"must_not": [
{"match": {"manufacturer.raw": "encore software"}},
{"match": {"manufacturer.raw": "global software publishing"}}
]
}
}
}
}
}
}
}

返回结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
...
"value" : 1363,
"relation" : "eq"
},
"max_score" : null,
"hits" : [ ]
},
"aggregations" : {
"bucket_class" : {
"buckets" : {
"encore" : {
"doc_count" : 76
},
"global" : {
"doc_count" : 11
},
"other" : {
"doc_count" : 1276
}
}
}
}
}

6. 收集日期/时间数据

Elasticsearch有一个非常强大的日期直方图聚合。使用日期直方图聚合,我们将看看我们如何创建存储桶在日期字段上。在此过程中,我们将经历以下几个阶段:

(1)跨时间段创建存储桶

//以天为单位进行数据切片

1
2
3
4
5
6
7
8
9
10
11
GET /filebeat-7.2.0-2019.08.30-000001/_search?size=0
{
"aggs": {
"counts_over_time": {
"date_histogram": {
"field": "@timestamp",
"fixed_interval": "1d"
}
}
}
}

结果为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
...
"aggregations" : {
"counts_over_time" : {
"buckets" : [
{
"key_as_string" : "2018-12-27T00:00:00.000Z",
"key" : 1545868800000,
"doc_count" : 537
},
{
"key_as_string" : "2018-12-28T00:00:00.000Z",
"key" : 1545955200000,
"doc_count" : 394
},
{
"key_as_string" : "2018-12-29T00:00:00.000Z",
"key" : 1546041600000,
"doc_count" : 69
},
...

返回的存储桶包含两种形式的密钥,key和 key_as_string 。key 字段自纪元(1970年1月1日)起以毫秒为单位,key_as_string 是 UTC 时间格式。

(2)使用不同的时区

如果我们想使用 IST 时区进行切片,可以指定 time_zone 参数。

1
2
3
4
5
6
7
8
9
10
11
12
GET /filebeat-7.2.0-2019.08.30-000001/_search?size=0
{
"aggs": {
"counts_over_time": {
"date_histogram": {
"field": "@timestamp",
"fixed_interval": "1d",
"time_zone": "+08:00"
}
}
}
}

返回结果为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
...
"aggregations" : {
"counts_over_time" : {
"buckets" : [
{
"key_as_string" : "2018-12-27T00:00:00.000+08:00",
"key" : 1545840000000,
"doc_count" : 521
},
{
"key_as_string" : "2018-12-28T00:00:00.000+08:00",
"key" : 1545926400000,
"doc_count" : 410
},

当度量标准聚合嵌套在桶聚合内时,将在每个桶中计算度量标准聚合。

管道聚合

管道聚合,也就是允许您对另一个聚合的结果进行聚合。它们允许您将聚合的结果作为输入导入另一个聚合。管道聚合是一个相对较新的特性,还没具体研究。