贫瘠之地

华北无浪漫,死海扬起帆
多少个夜晚,独自望着天

0%

ES 折叠操作

背景

折叠和聚合

日常会有很多场景希望将平面数据按照一定的条件组合起来,并按照一定规则进行计算

聚合将您的数据汇总为指标、统计或其他分析

可以帮助你回答类似如下问题:

  • 我的网页平均加载时间是多少?
  • 根据交易量谁是我最有价值的客户?
  • 在我的网站上大文件的衡量标准是多少?
  • 每个产品类别有多少种产品?

在关系型数据库中,聚合以 GROUP BY 关键字和聚合函数的方式进行实现

例如统计每个班级下男生的人数

1
SELECT `classId`,COUNT(*) AS 'number' FROM `student` WHERE `gender` = "male" GROUP BY `classId`;

按照班级即 classId 字段进行聚合,使用 COUNT() 聚合函数来统计数量

模拟

模拟一个场景来使用 ES 实现需求,设置这样一个索引

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
PUT /student-index/_mapping
{
"properties": {
"name": {
"type": "keyword"
},
"age": {
"type": "integer"
},
"gender": {
"type": "keyword"
},
"class": {
"type": "integer"
},
"height": {
"type": "float"
}
}
}

模拟数据

1
2
3
4
5
6
7
8
POST /student-index/_doc
{"name":"张三","age":22,"gender":"male","class":1,"height":70}
{"name":"李四","age":19,"gender":"male","class":2,"height":72.5}
{"name":"王五","age":20,"gender":"male","class":3,"height":80}
{"name":"小明","age":21,"gender":"male","class":2,"height":75}
{"name":"小红","age":19,"gender":"female","class":1,"height":50}
{"name":"小兰","age":18,"gender":"female","class":3,"height":51}
{"name":"蛋蛋","age":20,"gender":"male","class":1,"height":90}

折叠

在 ES 除了聚合 aggregations 外,有一种类似聚合的操作 collapse 折叠

你可以使用 collapse 参数来基于字段值查询结果;每个折叠仅选择排序靠前的文档来完成折叠

这里就可以看出折叠和聚合还是有使用上的区别的,聚合更关注的是聚合后的结果,而折叠是在原结果集基础上将字段去重,并且可以像分页一样对折叠结果进行分页

例如这样一组数据

name class time
张三 1 1
李四 2 2
王五 1 3

根据 time 倒序,按照 class 进行折叠,size 为 1,返回的结果应该是 class = 1 的数据,因为命中了 name = "王五" 的文档

from 为 1 时,返回 class = 2,此时所有折叠后的数据都已经查询完成

折叠更像是对于查询的一种特殊操作,其参数使用也和 query 在同一层级,返回体中也是在 hits 中返回文档

折叠查询

对于模拟的数据,对 class 字段折叠,班级的顺序按照男生的年龄排序,取第二名

即全校年龄第二大的男性学生所在的班级

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
POST /student-index/_search
{
"query": {
"match": {
"gender": "male"
}
},
"collapse": {
"field": "class"
},
"sort": [
{
"age": {
"order": "desc"
}
}
],
"from": 1, // 不查询第一条
"size": 1
}

返回的结果,只取 hits 部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
"hits": [
{
"_index": "student-index",
"_type": "_doc",
"_id": "k-mmzoQBCTaFCHoW7hai",
"_score": null,
"_source": {
"name": "小明",
"age": 21,
"gender": "male",
"class": 2,
"height": 75
},
"fields": {
"class": [
2
]
},
"sort": [
21
]
}
]

可以看到,_source 结果选取了折叠所命中的第一个文档(折叠的依据);fields 则是折叠的字段,说明年龄第二大的男生所在的班级是 2 班

扩展结果

上面折叠的简单操作可以看到,返回的 hits 中包含了一个折叠依据的文档(排序条件的第一个文档)

如果需要返回折叠字段下的所有文档(我认为更像是分组操作),可以使用 inner_hit 参数实现

取全校年龄第二大的男性学生所在的班级下面的所有学生,且按照身高正序排列

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
POST /student-index/_search
{
"query": {
"match": {
"gender": "male"
}
},
"collapse": {
"field": "class",
"inner_hits": {
"name": "student_in_class", // 自定的结果集名字
"size": 100, // inner_hits 内文档数量
"sort": [ // 这个排序参数是 inner hit,内部文档的排序
{
"height": "asc"
}
]
}
},
"sort": [ // 这个排序参数是查询条件的排序
{
"age": {
"order": "desc"
}
}
],
"from": 1,
"size": 1
}

返回结果前半部分和折叠查询一样,只取 inner_hit 字段下的结果集

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
"inner_hits": {
"student_in_class": {
"hits": {
"total": {
"value": 2,
"relation": "eq"
},
"max_score": null,
"hits": [
{
"_index": "student-index",
"_type": "_doc",
"_id": "kemmzoQBCTaFCHoWyxaJ",
"_score": null,
"_source": {
"name": "李四",
"age": 19,
"gender": "male",
"class": 2,
"height": 72.5
},
"sort": [
72.5
]
},
{
"_index": "student-index",
"_type": "_doc",
"_id": "k-mmzoQBCTaFCHoW7hai",
"_score": null,
"_source": {
"name": "小明",
"age": 21,
"gender": "male",
"class": 2,
"height": 75
},
"sort": [
75
]
}
]
}
}
}

可以看到 inner_hits 下是 student_in_class 自定的结果集名称,也就意味着一次 inner_hits 可以设置多个不同规则的结果集请求(inner_hits 参数是个数组)

返回的结果中包含了 2 班下的两名学生,并且根据 height 字段进行了排序

search after

折叠操作同样支持 search after 操作

因为本质上 ES 也是排序后根据文档顺序对折叠字段进行操作,当 search after 字段略过该值后,继续对未折叠字段值进行折叠操作

但是需要注意,只有当折叠字段和排序字段是同一字段时才能使用该方式,同时不允许二级排序

语法和基本查询一致

1
2
3
4
5
6
7
8
9
10
11
12
{
"query": {
"match": {
"message": "GET /search"
}
},
"collapse": {
"field": "user.id"
},
"sort": [ "user.id" ],
"search_after": ["dd5ce1ad"]
}

二级折叠

对某一个字段折叠后,inner_hits 内还可以对其他字段再进行一次折叠

但二次折叠无法使用 inner_hits 参数,即折叠只能支持到二级

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
{
"query": {
"match": {
"gender": "male"
}
},
"collapse": {
"field": "class",
"inner_hits": {
"name": "student_in_class",
"size": 100,
"collapse": {
"field": "height"
}
}
},
"sort": [
{
"age": {
"order": "desc"
}
}
],
"size": 1
}

根据 class 折叠后的内部文档,再根据 height 进行折叠操作