要使用Elasticsearch進(jìn)行分組聚合統(tǒng)計,可以使用聚合(aggregation)功能。聚合操作允許您根據(jù)指定的條件對文檔進(jìn)行分組,并計算每個分組的聚合結(jié)果。
針對普通類型的字段,DSL構(gòu)建語法:
{
"aggs": {
"agg_name": {
"agg_type": {
"agg_parameters"
}
},
"agg_name2": {
"agg_type": {
"agg_parameters"
}
},
...
}
}
aggs:?aggregations關(guān)鍵字的別名,代表著分組
agg_name: 這個是自定義的名字,可以針對你自己的字段命名一個,最好加上_agg后綴
agg_type: 聚合類型
agg_parameters:聚合參數(shù)
聚合類型(agg_type)
Elasticsearch中支持多種聚合類型(agg_type)用于不同的聚合操作。以下是一些常用的聚合類型及其功能:
- Terms(詞條聚合):按照字段值進(jìn)行分組,統(tǒng)計每個分組的文檔數(shù)量。
- Sum(求和聚合):計算指定字段的總和。
- Avg(平均值聚合):計算指定字段的平均值。
- Min(最小值聚合):找出指定字段的最小值。
- Max(最大值聚合):找出指定字段的最大值。
- Stats(統(tǒng)計聚合):計算指定字段的統(tǒng)計信息,包括最小值、最大值、總和、平均值和文檔數(shù)量。
- Extended Stats(擴(kuò)展統(tǒng)計聚合):計算指定字段的擴(kuò)展統(tǒng)計信息,包括最小值、最大值、總和、平均值、標(biāo)準(zhǔn)差和文檔數(shù)量。
- Cardinality(基數(shù)聚合):計算指定字段的唯一值數(shù)量。
- Date Histogram(日期直方圖聚合):按照時間間隔對日期字段進(jìn)行分組。
- Range(范圍聚合):將文檔按照指定范圍進(jìn)行分組,例如按照價格范圍、年齡范圍等。
- Nested(嵌套聚合):在嵌套字段上執(zhí)行子聚合操作。
除了上述示例外,Elasticsearch還提供了更多聚合類型,如Geo Distance(地理距離聚合)、Date Range(日期范圍聚合)、Filter(過濾聚合)等。
聚合參數(shù)(agg_parameters)
在Elasticsearch中,聚合(aggregation)可以使用不同的參數(shù)來控制其行為和結(jié)果。以下是一些常用的聚合參數(shù):
1. field(字段):指定要聚合的字段。
2. size(大?。合拗品祷氐木酆贤暗臄?shù)量。
3. script(腳本):使用腳本定義聚合邏輯。
4. min_doc_count(最小文檔數(shù)量):指定聚合桶中文檔的最小數(shù)量要求。
5. order(排序):按照指定字段對聚合桶進(jìn)行排序。
6. include/exclude(包含/排除):根據(jù)指定的條件包含或排除聚合桶。
7. format(格式):對聚合結(jié)果進(jìn)行格式化。
8. precision_threshold(精度閾值):用于基數(shù)聚合的精度控制。
9. interval(間隔):用于日期直方圖聚合的時間間隔設(shè)置。
10. range(范圍):用于范圍聚合的范圍定義。
具體可用的參數(shù)取決于聚合類型和使用的Elasticsearch版本。
DSL查詢實(shí)踐
準(zhǔn)備工具:?Kibana或者Elasticvue
在這里,我使用Elasticvue
網(wǎng)址:Elasticvue - Elasticsearch gui for the browser
這個工具我是裝在火狐上的,連接上后能看到節(jié)點(diǎn)信息、集群健康、索引信息等等,也支持REST查詢,類似在Kibana使用Devtools差不多。
單個分組DSL查詢, 求分組后的平均值
{
"size": 0,
"aggs": {
"id_agg": {
"terms": {
"field": "id",
"size": 3 #在有的情況下,如果你的文檔數(shù)量太多,會導(dǎo)致查詢超時、返回數(shù)據(jù)過多的問題
},
"aggs": {
"sub_id_agg": {
"terms": { #匹配搜索
"field": "id"
}
}
}
}
}
}
?
?這張圖上面有幾個關(guān)鍵信息
`/orderv4/order/_search` 是一個 Elasticsearch 的 REST API 端點(diǎn),用于執(zhí)行針對名為 `order` 的索引的搜索操作。
- /orderv4/order: 表示索引的名稱是 `orderv4`,類型(Type)的名稱是 `order`。
- 在較新的 Elasticsearch 版本中,類型的概念已經(jīng)逐漸被棄用,因此索引名稱后面的 `/order` 可以省略。
- _search: 表示執(zhí)行搜索操作。
左側(cè)是DSL請求體,右邊是返回結(jié)果
took: 執(zhí)行搜索的時間,單位是毫秒
timed_out:搜索是否超時
_shards:分片執(zhí)行情況,這里的total代表參與搜索的總分片數(shù)
hits:和搜索文檔匹配的文檔信息,total代表和搜索條件匹配的總文檔數(shù)
aggregations:里面是聚合結(jié)果,id_agg是剛才在dsl查詢的時候設(shè)置的聚合名稱,sum_other_doc_count代表除了bucket里面的文檔數(shù)量,還有多少條沒有展示。buckets里面的key就是文檔里面的id的值是多少,doc_count 表示文檔數(shù)量,換句話來說就是,id = 0 的數(shù)量為 1
使用Java構(gòu)建分組查詢
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
// 添加聚合操作
TermsAggregationBuilder aggregationBuilder = AggregationBuilders.terms("id_agg").field("id");
aggregationBuilder.subAggregation(AggregationBuilders.terms("sub_id_agg").field("id"));
searchSourceBuilder.aggregation(aggregationBuilder);
基于nested嵌套類型分組查詢
nested(嵌套)是一種特殊的數(shù)據(jù)類型和查詢方式,用于處理嵌套文檔結(jié)構(gòu)。它允許在文檔中嵌套其他文檔,并以一種有層次結(jié)構(gòu)的方式進(jìn)行索引和查詢。
在使用nested查詢的時候,先要對你的索引設(shè)置Mapping配置。把字段類型設(shè)置為nested。
一種是在建索引的時候,就配好Mapping,另外一種方式是直接對索引文檔更新。
POST youer_index/_mapping/your_type
{
"properties":{
"item_list":{ # 在Java的ESDO模型里,就代表了一個List<Item>, Item是你自己定義的業(yè)務(wù)對象
"type":"nested", #給item_list設(shè)置嵌套類型
"properties":{
"id":{
"type":"long"
},
"name":{
"type":"string"
},
"price":{
"type":"long"
}
}
}
}
}
nested字段DSL查詢案例
{
"aggregations":{
"item_list_agg":{
"nested":{
"path":"item_list" # 字段路徑必須,不然查不出結(jié)果
},
"aggs":{
"sub_item_list_agg":{
"terms":{
"field":"item_list.id"
}
}
}
}
}
}
使用Java構(gòu)建nested分組查詢
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
// 添加聚合操作
AggregationBuilder nested = AggregationBuilders.nested("id_nested_agg", "item_list");
// 構(gòu)建一個terms
TermsAggregationBuilder terms = AggregationBuilders.terms("id_nested_sub_agg").field("id");
// 將terms加到nested中
nested.subAggregation(terms);
// 添加到最終的查詢中
searchSourceBuilder.aggregation(nested);
更多的案例,如果有興趣的朋友可以自己摸索。下面我就分享一個實(shí)戰(zhàn)中,如何用Java針對普通字段類型和nested字段類型構(gòu)建查詢語句,同時支持返回多個字段值。文章來源:http://www.zghlxwxcb.cn/news/detail-553373.html
import com.github.houbb.heaven.util.lang.StringUtil;
import org.apache.commons.collections.CollectionUtils;
import org.elasticsearch.search.aggregations.AggregationBuilder;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
/**
* @author : kenny
* @since : 2023/5/18
**/
public class AggregationBuilderExample {
/**
* 構(gòu)造一個單桶分組查詢,支持普通字段類型和nested字段類型
* @param aggregationFields
* @return
*/
public static AggregationBuilder buildSingeBucketAggregationBuilder(List<String> aggregationFields) {
if (CollectionUtils.isEmpty(aggregationFields)) {
throw new RuntimeException("Aggregate search requires aggregate fields!");
}
aggregationFields = aggregationFields.stream().filter(StringUtil::isNotEmpty).collect(Collectors.toList());
String aggregationField = aggregationFields.get(0);
int dotIndex = aggregationField.indexOf(".");
AggregationBuilder aggregationBuilder;
if (dotIndex != -1) {
String path = aggregationField.substring(0, dotIndex);
aggregationBuilder = AggregationBuilders.nested(aggregationField + "nested_agg", path);
AggregationBuilder nestedTerms = AggregationBuilders.terms(aggregationField).field(aggregationField).size(1000);
aggregationBuilder = aggregationBuilder.subAggregation(nestedTerms);
}else {
aggregationBuilder = AggregationBuilders.terms(aggregationField + "_agg").field(aggregationField).size(1000);
}
return aggregationBuilder;
}
/**
* 構(gòu)造一個多桶分組查詢,支持普通字段類型和nested字段類型
* @param aggregationFields
* @return
*/
public static List<AggregationBuilder> buildMultiplexBucketAggregationBuilder(List<String> aggregationFields){
if (CollectionUtils.isEmpty(aggregationFields)) {
throw new RuntimeException("Aggregate search requires aggregate fields!");
}
aggregationFields = aggregationFields.stream().filter(StringUtil::isNotEmpty).collect(Collectors.toList());
List<AggregationBuilder> aggregations = new ArrayList<>();
for (String field : aggregationFields){
int dotIndex = field.indexOf(".");
AggregationBuilder aggregationBuilder;
if (dotIndex != -1) {
String path = field.substring(0, dotIndex);
aggregationBuilder = AggregationBuilders.nested(field + "_nested_agg", path);
AggregationBuilder nestedTerms = AggregationBuilders.terms(field).field(field).size(1000);
aggregationBuilder = aggregationBuilder.subAggregation(nestedTerms);
}else {
aggregationBuilder = AggregationBuilders.terms(field).field(field).size(1000);
}
aggregations.add(aggregationBuilder);
}
return aggregations;
}
}
針對于結(jié)果的解析我們同樣也構(gòu)造一個解析方法文章來源地址http://www.zghlxwxcb.cn/news/detail-553373.html
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author : kenny
* @since : 2023/5/18
**/
public class AggregationResultParserExample {
/**
* 針對單桶聚合統(tǒng)計
* @param json ES執(zhí)行搜索之后返回的MetricAggregation信息
* @return
*/
public static Map<String, Integer> parseSingleBucketAggregations(String json) {
JSONObject jsonObject = JSONObject.parseObject(json);
if (jsonObject == null){
return null;
}
Map<String, Integer> resultMap = new HashMap<>();
try {
internal_ParseSingBucketAggregations(jsonObject, resultMap);
}catch (Exception ex){
// 處理你自己的異常
}
return resultMap;
}
private static void internal_ParseSingBucketAggregations(JSONObject jsonObject, Map<String, Integer> map) {
for (Map.Entry<String, Object> entry : jsonObject.entrySet()) {
String key = entry.getKey();
Object value = entry.getValue();
if (value instanceof JSONObject) {
JSONObject childObject = (JSONObject) value;
if (childObject.containsKey("key") && childObject.containsKey("doc_count")) {
String childKey = childObject.getJSONObject("key").getString("value");
int docCount = childObject.getJSONObject("doc_count").getIntValue("value");
map.put(childKey, docCount);
}
internal_ParseSingBucketAggregations(childObject, map);
} else if (value instanceof JSONArray) {
JSONArray childArray = (JSONArray) value;
for (Object element : childArray) {
if (element instanceof JSONObject) {
JSONObject childObject = (JSONObject) element;
internal_ParseSingBucketAggregations(childObject, map);
}
}
}
}
}
/**
* 解析多桶分組統(tǒng)計
* @param json ES執(zhí)行搜索之后返回的MetricAggregation信息
* @return
*/
public static Map<String, List<Map<String, Object>>> parseMultiplexBucketAggregations(String json) {
JSONObject jsonRoot = JSONObject.parseObject(json);
if (jsonRoot == null){
return Collections.emptyMap();
}
Map<String, List<Map<String, Object>>> resultMap = new HashMap<>();
try {
internal_ParseMultiplexBucketAggregations(jsonRoot, "", resultMap);
}catch (Exception ex){
// 處理你自己的異常
}
return resultMap;
}
private static void internal_ParseMultiplexBucketAggregations(JSONObject jsonObject, String prefix, Map<String, List<Map<String, Object>>> resultMap) {
for (Map.Entry<String, Object> entry : jsonObject.entrySet()) {
String key = entry.getKey();
Object value = entry.getValue();
if (value instanceof JSONObject) {
JSONObject childObject = (JSONObject) value;
if (childObject.containsKey("buckets")) {
List<Map<String, Object>> bucketList = new ArrayList<>();
JSONArray buckets = childObject.getJSONObject("buckets").getJSONArray("elements");
for (int i = 0; i < buckets.size(); i++) {
JSONObject bucket = buckets.getJSONObject(i);
Map<String, Object> bucketMap = new HashMap<>();
JSONObject bucketMembers = bucket.getJSONObject("members");
for (Map.Entry<String, Object> bucketEntry : bucketMembers.entrySet()) {
String bucketKey = bucketEntry.getKey();
Object bucketValue = bucketEntry.getValue();
if (bucketValue instanceof JSONObject) {
JSONObject valueObject = (JSONObject) bucketValue;
if (valueObject.containsKey("value")) {
bucketMap.put(bucketKey, valueObject.get("value"));
}
}
}
bucketList.add(bucketMap);
}
resultMap.put(prefix + key, bucketList);
} else {
internal_ParseMultiplexBucketAggregations(childObject, prefix + key + "_", resultMap);
}
}
}
}
}
到了這里,關(guān)于使用Elasticsearch進(jìn)行分組聚合統(tǒng)計的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!