前言
之前写过一篇使用RestHighLevelClient进行多字段group by的(【原创】ElasticSearch使用Java代码group by多个字段查询统计数量_DCTANT的博客-CSDN博客_elasticsearch分组统计 java),其实那篇并不完美,最佳的group by方法应该用script,而不是像之前那篇博客一样去递归求和,虽然也不是不行,但是这个毕竟太暴力了,效率也不高。
直接上代码:
初始化RestHighLevelClient我这边为了节省篇幅先跳过了,直接上核心:
SearchRequest searchRequest = new SearchRequest(index);
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
//为了保持顺序,使用LinkedHashMap,防止进入map后变成乱序排列
LinkedHashMap<String, Long> nameCountMap = new LinkedHashMap<>();
//group by后再order by
BucketOrder bucketOrder = BucketOrder.count(false); //这里的count方法中true表示升序排列,false代表降序排列
AggregationBuilder aggregationBuilder = AggregationBuilders.terms("${分组名}") //分组名可以随便填,但是必须和后面aggregations.get("${分组名}");里面填的分组名保持一致,否则会返回null,导致后续空指针异常
.script(new Script("doc['${字段1}'].value+'@@'+doc['${字段2}'].value+'@@'+doc['${字段3}'].value+'@@'+doc['${字段4}'].value")) //相当于group by 4个字段,其中的@@是字段间的分隔符,最后ES返回的结果是这样的${字段1}@@${字段2}@@${字段3}@@${字段4}
.size(20) //聚合返回20个值(不传默认返回10个值)
.order(bucketOrder); //将上方的排序填入,让ES进行排序
sourceBuilder.aggregation(aggregationBuilder); //填入分组信息
searchRequest.source(sourceBuilder);
try {
SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
Aggregations aggregations = searchResponse.getAggregations();
Terms terms = aggregations.get("${分组名}"); //填入上方terms()中相同的分组名
for (Terms.Bucket bucket : terms.getBuckets()) {
String key = bucket.getKeyAsString(); //这里的key值是这样的:${字段1}@@${字段2}@@${字段3}@@${字段4}
long count = bucket.getDocCount(); //group by后聚合统计出来的数量
String[] split = key.split("@@"); //切开作为分隔符的@@,取里面需要的字段放入nameCountMap就行了
nameCountMap.put(split[0], count); //假设取第0个字段作为key
}
} catch (IOException e) {
e.printStackTrace();
}
// todo 处理剩下的业务逻辑
看一下注释应该就能懂了,相当于ES的查询为:
{
"aggs":{
"myStatistic":{
"terms":{
"script":{
"inline":"doc['字段1'].value+'@@'+doc['字段2'].value+'@@'+doc['字段3'].value+'@@'+doc['字段4'].value"
},
"size":20,
"order":{
"_count":"desc"
}
}
}
}
}
参考链接:Elasticsearch入门 ,使用 script 做聚合查询 - 简书
如果script中的字段,其中有个值不存在则会报这个错:
解决方案如这个博客所示,我这边不再赘述