聚合查询
前言
聚合可以算得上ES中最难用的一部分了,其API也是最反人类的,懂的都懂。对聚合的支持,SpringData-ElasticSearch直接放弃了,EE对其ES的聚合的简化也比较有限,尽管如此,也算得上目前市面上开源框架中对ES聚合支持得最好的框架了,大家在用得时候请轻喷,在聚合方式排列组合有上千种,以及灵活多变的树形桶得数据解析上,我们真的尽力了,奉劝大家抛弃幻想,准备迎接挑战,这不是MySQL。ES的聚合和MySQL的Group By聚合完全是两种不同的东西。 您现在看到的聚合也绝对不是EE对聚合支持的最终形态,我们还会持续探索进一步简化聚合,来减轻用户使用ES聚合功能的烦恼。
# 常规聚合
在MySQL中,我们可以通过指定字段进行group by聚合,EE同样也支持类似聚合:
@Test
public void testGroupBy() {
LambdaEsQueryWrapper<Document> wrapper = new LambdaEsQueryWrapper<>();
wrapper.likeRight(Document::getContent,"推");
wrapper.groupBy(Document::getCreator);
// 支持多字段聚合
// wrapper.groupBy(Document::getCreator,Document::getCreator);
SearchResponse response = documentMapper.search(wrapper);
System.out.println(response);
}
2
3
4
5
6
7
8
9
10
设置聚合桶大小及桶内排序
@Test
@Order(6)
public void testConditionGroupBy() {
LambdaEsQueryWrapper<Document> wrapper = new LambdaEsQueryWrapper<>();
wrapper.match(Document::getContent, "测试")
.size(20) // 设置聚合桶大小
.bucketOrder(BucketOrder.count(Boolean.TRUE)) // 设置桶排序规则
.groupBy(Document::getStarNum);
SearchResponse response = documentMapper.search(wrapper);
System.out.println(response);
}
2
3
4
5
6
7
8
9
10
11
温馨提示
尽管语法与MP一致,但实际上,ES的聚合结果是放在单独的对象中的,格式如下所示,因此我们高阶语法均需要用SearchResponse来接收返回结果,这点需要区别于MP和MySQL.
"aggregations":{"sterms#creator":{"doc_count_error_upper_bound":0,"sum_other_doc_count":0,"buckets":[{"key":"老汉","doc_count":2},{"key":"老王","doc_count":1}]}}
# 其它聚合:
// 求最小值
wrapper.min();
// 求最大值
wrapper.max();
// 求平均值
wrapper.avg();
// 求和
wrapper.sum();
2
3
4
5
6
7
8
如果需要先groupBy,再根据groupBy聚合后桶中的数据进行求最值,均值之类的,也是支持的,会按照您在wrapper中指定的顺序,管道聚合(pipeline aggregation).
示例:
@Test
public void testAgg() {
// 根据创建者聚合,聚合完在该桶中再次根据点赞数聚合
// 注意:指定的多个聚合参数为管道聚合,就是第一个聚合参数聚合之后的结果,再根据第二个参数聚合,对应Pipeline聚合
LambdaEsQueryWrapper<Document> wrapper = new LambdaEsQueryWrapper<>();
wrapper.eq(Document::getTitle, "老汉")
.groupBy(Document::getCreator)
.max(Document::getStarNum);
SearchResponse response = documentMapper.search(wrapper);
System.out.println(response);
}
2
3
4
5
6
7
8
9
10
11
另外EE还提供了可以配置是否开启管道聚合的参数,默认为开启,如果你想让多个字段聚合的结果出现在各自的桶中,那么你可以指定enablePipeline参数为false即可.
@Test
public void testAggNotPipeline() {
// 对于下面两个字段,如果不想以pipeline管道聚合,各自聚合的结果在各自的桶中展示的话,我们也提供了支持
LambdaEsQueryWrapper<Document> wrapper = new LambdaEsQueryWrapper<>();
// 指定启用管道聚合为false
wrapper.groupBy(false, Document::getCreator, Document::getTitle);
SearchResponse response = documentMapper.search(wrapper);
System.out.println(response);
}
2
3
4
5
6
7
8
9
# 去重聚合
为了方便用户去重,我们针对字段的去重提供了极为友好的方式,解决用户根据字段进行去重及分页要写大量代码来实现的烦恼,使用ee单字段去重仅需1行即可搞定!
API:
// 去重,入参为去重列
wrapper.distinct(R column);
2
下面我用一段代码来演示根据指定字段去重:
@Test
public void testDistinct() {
// 查询所有标题为老汉的文档,根据创建者去重,并分页返回
LambdaEsQueryWrapper<Document> wrapper = new LambdaEsQueryWrapper<>();
wrapper.eq(Document::getTitle, "老汉")
.distinct(Document::getCreator);
PageInfo<Document> pageInfo = documentMapper.pageQuery(wrapper, 1, 10);
System.out.println(pageInfo);
}
2
3
4
5
6
7
8
9
坏味道
对于多字段的去重支持,没有上述这么简单,因为多字段去重无法通过折叠去实现,数据会被置入桶中返回,桶中数据的解析,需要哪些字段,排序规则,覆盖规则是怎样的过于灵活,我们无法通过框架来帮用户屏蔽这些,市面上目前也无任何框架能够支撑,因此,我们对多字段的去重仅支持到了查询条件的封装,数据解析部分需用户自行完成,敬请谅解.好在多字段去重的场景并不是太多,用户如果有用到多字段去重,可参见本篇开头对groupBy的介绍,可通过groupBy对多字段实现去重。