背景
折叠和聚合
日常会有很多场景希望将平面数据按照一定的条件组合起来,并按照一定规则进行计算
聚合将您的数据汇总为指标、统计或其他分析
可以帮助你回答类似如下问题:
- 我的网页平均加载时间是多少?
- 根据交易量谁是我最有价值的客户?
- 在我的网站上大文件的衡量标准是多少?
- 每个产品类别有多少种产品?
在关系型数据库中,聚合以 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
参数来基于字段值查询结果;每个折叠仅选择排序靠前的文档来完成折叠
这里就可以看出折叠和聚合还是有使用上的区别的,聚合更关注的是聚合后的结果,而折叠是在原结果集基础上将字段去重,并且可以像分页一样对折叠结果进行分页
例如这样一组数据
根据 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, "sort": [ { "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
进行折叠操作