RestHighLevelClient進階-聚合操作


之前的博客寫過Spring與RestHighLevelClient的基礎用法,但是實際使用中,會存在大量的復雜操作,如分組,聚合等。

接下來我們就來看下不太好用的分組聚合基本用法

AggregationBuilder
  • 首先RestHighLevelClient中分組需要用到AggregationBuilder,作為分組條件的構建。
    我們可以看到AggregationBuilder接口有很多實現類,比如AvgAggregationBuilder、MaxAggregationBuilder、SumAggregationBuilder等常用的統計函數。

在使用上還是和之前的大致一樣,

SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
// 以batchId為分組條件,terms為分組后的字段名稱,field為將被分組的字段名稱
TermsAggregationBuilder aggregation = AggregationBuilders.terms("batchId").field("batchId.keyword")
	// 分組求和integral字段,並將求和后的字段名改為score
	// subAggregation為子聚合,即在batchId分組后的小組內聚合
	.subAggregation(AggregationBuilders.sum("score").field("integral"))
	// 注意這里,下面介紹
	.subAggregation(AggregationBuilders.topHits("details").size(1));

sourceBuilder.aggregation(aggregation);

BoolQueryBuilder boolBuilder = QueryBuilders.boolQuery();
sourceBuilder.query(boolBuilder);

SearchRequest searchRequest = new SearchRequest("table");
searchRequest.source(sourceBuilder);

SearchResponse search = client.search(searchRequest);
// 和之前不同的是這里需要getAggregations獲取聚合后的數據
Aggregations aggregations = search.getAggregations();
// 從分組后的數據中拿到batchId的數據,這里以batchId分組,則分組后的數據都在batchId里
ParsedStringTerms terms = aggregations.get("batchId");
// 獲取到分組后的所有bucket
List<? extends Terms.Bucket> buckets = terms.getBuckets();

for (Terms.Bucket bucket : buckets) {
	// 解析bucket 因為一級聚合為以batchId分組,二級聚合為求和,所以這里還需要getAggregations獲取求和的數據
	Aggregations bucketAggregations = bucket.getAggregations();
	// 這里我是通過debug才找到返回的參數類型的,我不知道在哪找得到這個東西,所以我們拿到了ParsedTopHits,這里我們是取了一個,所以這個值的數組長度為1
	ParsedTopHits topHits = bucketAggregations.get("details");
	// 因為求和和下面的topHits都是AggregationBuilders.terms("batchId").field("batchId.keyword")的subAggreation,所以都屬於batchId組內
	// 獲取到求和的數據信息
	ParsedSum sum = bucketAggregations.get("integral");
	// 因為topHits中命中的hits肯定至少有一個,要不然也不會成組,所以這里直接獲取第一個,並解析成map
	Map<String, Object> sourceAsMap = topHits.getHits().getHits()[0].getSourceAsMap();
	// 將求和后的integral覆蓋到原數據中
	sourceAsMap.put("integral", sum.value());
	// 打印出統計后的數據
	System.out.println(sourceAsMap);
}

注意點:

  1. 為什么要用subAggregation(AggregationBuilders.topHits("details").size(1))?
  • 這里有個坑,如果只用batchId分組並用intrgal求和,那么返回值中只會返回batchId與integral求和后的值這兩個字段。這里舉個例子,現在統計全國所有學校的總人數,那么我們以school分組,求和了人數。但是我們仍然想知道每個學校屬於哪個地區的,這里聚合后就不會返回這些信息,讓人很難受。
  • 此時我們就可以使用subAggregation(AggregationBuilders.topHits("details").size(1))來獲取一個通用的數據,拿到學校所屬的地區名稱或者其他學校的上級屬性。
  1. AggregationBuilders層級
  • 我們注意到subAggregation方法是AggregationBuilder的,所以在構建AggregationBuilders的時候,每一個子AggregationBuilder都可以有子AggregationBuilder,如下
	TermsAggregationBuilder aggregation = AggregationBuilders.terms("batchId").field("batchId.keyword")
		.subAggregation(
				AggregationBuilders.terms("folder").field("folder")
						.subAggregation(AggregationBuilders.sum("integral").field("integral"))
	);
  • 這種就是組內再聚合套組內,但是得注意字段的問題,即若你以batchId分組后,那么組內只有batchId與其他的字段,就和第一個注意點一樣,導致組內聚合拿不到相關的字段,導致報錯。

結尾

  • 研究了半天最終還是沒有用,因為太復雜。還不知道怎么同時以兩個條件分組,類似於mysql的groupby a,b
  • 太難了

更新與2020/10/28 我胡漢三又回來啦

最終還是使用了ES的分組,上面的問題也得以解決。

多條件聚合
  • 上面我們說了聚合操作,但是結尾處說了不知道怎么同時以兩個條件分組,類似於mysql的groupby a,b,下面我們來看下怎么以多字段分組;
Script script = new Script("doc['userId.keyword'].values +'#'+ doc['taskId.keyword'].values");
AggregationBuilder aggregation = AggregationBuilders.cardinality("user").script(script);
sourceBuilder.aggregation(aggregation);
  • 我們可以看到這里使用了script來解決了多字段分組聚合的問題,這里script的意思是以[userId]#[taskId]為唯一鍵來進行分組,當然這個script可以自己改,分隔符也不限定於#,我們可以改為"doc['userId.keyword'].values +'####'+ doc['taskId.keyword'].values + '####' doc[batchId.keyword].values"來完成三字段分組。

  • 這樣我們從bucket中可以獲取到key,則key的格式即為[userId]#[taskId],而getAggregations則為聚合的數據


更新與2020/10/29
  • 上面說到聚合,然后今天自信滿滿的進行了自測,發現分頁出現了問題。 當時的寫法是這樣的:
	TermsAggregationBuilder aggregationBuilder = AggregationBuilders.terms("user").script(script)
		.subAggregation(AggregationBuilders.sum("integral").field("integral"));
	SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
	sourceBuilder.aggregation(aggregation);
	sourceBuilder.from(30);
	sourceBuilder.size(10);

當我嘗試變化from發現獲取的值永遠都是前十條。
查詢各種網站后得出的結論也並不樂觀,比如 https://blog.csdn.net/laoyang360/article/details/79112946

截取博客中一段話,如下:

  • 可以看到走正常的聚合是行不通的,於是看見了第三條,即此博主沒有深入研究的,推測是不是有可能實現。即一次取全部的最大值的聚合,然后再聚合的數據里進行排序分頁。
  • 最終代碼如下:
	TermsAggregationBuilder aggregationBuilder = AggregationBuilders.terms("user").script(script)
                .subAggregation(AggregationBuilders.sum("integral").field("integral"))
                .subAggregation(AggregationBuilders.topHits("sample").size(1)).size(9999);
        List<FieldSortBuilder> fieldSorts=new ArrayList<>();
        fieldSorts.add(new FieldSortBuilder("integral").order(SortOrder.DESC));
        aggregationBuilder.subAggregation(new BucketSortPipelineAggregationBuilder("bucket_field", fieldSorts).from(params.getOffset()).size(params.getPageSize()));

        Aggregations aggregations = IntegralQuery.aggregation(boolBuilder, aggregationBuilder);

不知道這樣是不是上面截圖中分區來取的意思,反正是實現了聚合分頁。前提聚合后的.size需要設置大一些,否則下面排序分頁會取不到。這種做法就是先進行聚合分組,然后再分組后bucket中進行排序分頁。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM