最近做一个需求时,需要对ES中的数据按某个字段分组统计个数,所以用到了ES的aggregations分组,我本以为ES默认会返回所有分组的结果,但实际看日志才发现aggregations的条件中默认带上了size=10,所以默认只会返回10条分组的结果
那么如果我想返回所有的结果怎么办?网上一般的做法是手动指定aggregation的size为一个很大的数,比如10000,这样就可以返回10000条分组结果了。本来我也打算这样做的,但是一个问题是,我确认了实际的数据,用kibana执行的结果中分组的条数有10万以上。所以这种方法就不太适用了,必须得分页获取了。
于是我开始在网上查如何分页获取aggregations中的分组结果,但是结果不令人满意,不管是百度,还是google,还是chatGPT,给出的方法都是使用search after或scroll来分页,但是这两种我都试验了下,都是查询明细的分页方式,而不是aggregations中的分页。
于是,我只能自己想办法实现了,基于search after的思路,我想到了一个办法,由于我分组的这个字段是一个雪花算法生成的id,所以是可以比较大小的,那么我aggregations里按这个分组字段升序,然后每次取到分组结果中的最后一条数据,下一次查询条件中带上id > 最后一条数据id,这样不就从上次最后一条数据之后开始取了吗?
于是写完代码验证无误。关键代码如下:
BoolQueryBuilder boolQueryBuilder = QueryBuilders .boolQuery() .mustNot(QueryBuilders.existsQuery("deleted_at")); boolQueryBuilder.must(queryBuilder); SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); searchSourceBuilder.query(boolQueryBuilder); searchSourceBuilder.sort(SortBuilders.fieldSort("user_id").order(SortOrder.ASC)); searchSourceBuilder.size(0); TermsAggregationBuilder aggregationBuilder = AggregationBuilders.terms("group_cnt") .field(groupByColumn).order(BucketOrder.key(true)); aggregationBuilder.size(10000); searchSourceBuilder.aggregation(aggregationBuilder); if (lastSortValue != null) { boolQueryBuilder.must(QueryBuilders.rangeQuery("user_id").gt(lastSortValue)); } SearchResponse searchResponse = this.search(esIndex, searchSourceBuilder); Terms aggregation = searchResponse.getAggregations().get("group_cnt"); if (ObjectUtils.isEmpty(aggregation.getBuckets())) { return result; } for (Terms.Bucket bucket : aggregation.getBuckets()) { String group = bucket.getKeyAsString(); Long count = bucket.getDocCount(); } Terms.Bucket lastBucket = aggregation.getBuckets().get(aggregation.getBuckets().size() - 1); lastSortValue = lastBucket.getKeyAsString();