ElasticSearch聚合查詢Restful語法和JavaApi詳解(基於ES7.6)

2022-01-05 Java魚仔
(一)概述

在前面關於ES的一系列文章中,已經介紹了ES的概念、常用操作、JavaAPI以及實際的一個小demo,但是在真實的應用場景中,還有可能會有更高階的一些用法,今天主要介紹兩種相對來說會更難一些的操作,聚合查詢。該文檔基於ElasticSearch7.6,將介紹restful查詢語法以及JavaApi。

閱讀本文需要你有ElasticSearch的基礎。

(二)前期數據準備

這裡準備了包含姓名、年齡、教室、性別和成績五個欄位的數據

PUT /test4
{
    "mappings" : {
      "properties" : {
        "name" : {
          "type" : "text"
        },
        "age":{
          "type": "integer"
        },
        "classroom":{
          "type": "keyword"
        },
        "gender":{
          "type": "keyword"
        },
        "grade":{
          "type": "integer"
        }
      }
    }
}

PUT /test4/_bulk
{"index": {"_id": 1}}
{"name":"張三","age":18,"classroom":"1","gender":"男","grade":80}
{"index": {"_id": 2}}
{"name":"李四","age":20,"classroom":"2","gender":"男","grade":60}
{"index": {"_id": 3}}
{"name":"王五","age":20,"classroom":"2","gender":"女","grade":70}
{"index": {"_id": 4}}
{"name":"趙六","age":19,"classroom":"1","gender":"女","grade":90}
{"index": {"_id": 5}}
{"name":"毛七","age":20,"classroom":"1","gender":"男","grade":90}

(三)聚合查詢

ES中的聚合操作提供了強大的分組及數理計算的能力,ES中聚合從大體上可以分為四種方式:

1、Metrics Aggregation 提供了諸如Max,Min,Avg的數值計算能力

2、Bucket Aggregation 提供了分桶的能力,簡單來講就是將一類相同的數據聚合到一起

3、Pipeline Aggregation 管道聚合,對其他聚合進行二次聚合

4、Matrix Aggregation 對多個欄位進行操作並返回矩陣結果

ES官網提供了全部聚合查詢文檔,這篇文章將介紹常用的幾種聚合查詢的語法以及JavaApi:

https://www.elastic.co/guide/en/elasticsearch/client/java-rest/7.6/java-rest-high-aggregation-builders.html#_metrics_aggregations

(四)Metrics Aggregation4.1 AVG

avg用於計算聚合文檔中提取的數值的平均值,restful查詢語法如下:

POST /test4/_search
{
  "aggs": {
    "avg_grade": {
      "avg": {
        "field": "grade"
      }
    }
  }
}

查詢得到的結果如下:

接著是JavaApi,核心在於使用AggregationBuilders的avg方法,第七行代碼對應於上面的操作。

@Test
public void testAvg() throws Exception {
    //封裝了獲取RestHighLevelClient的方法
    RestHighLevelClient client=ElasticSearchClient.getClient();
    SearchRequest request = new SearchRequest("test4");
    SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
    searchSourceBuilder.aggregation(AggregationBuilders.avg("agg_grade").field("grade")).size(0);
    request.source(searchSourceBuilder);
    SearchResponse search = client.search(request, RequestOptions.DEFAULT);
    //注意這裡要把Aggregation類型轉化為ParsedAvg類型
    ParsedAvg aggregation = search.getAggregations().get("agg_grade");
    System.out.println(aggregation.getValue()); //返回78.0
}

接下來就直接貼代碼了

4.2 Min

獲取聚合數據的最小值:

POST /test4/_search
{
  "aggs": {
    "min_grade": {
      "min": {
        "field": "grade"
      }
    }
  }
}

@Test
public void testMin() throws Exception {
    //封裝了獲取RestHighLevelClient的方法
    RestHighLevelClient client=ElasticSearchClient.getClient();
    SearchRequest request = new SearchRequest("test4");
    SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
    searchSourceBuilder.aggregation(AggregationBuilders.min("min_grade").field("grade")).size(0);
    request.source(searchSourceBuilder);
    SearchResponse search = client.search(request, RequestOptions.DEFAULT);
    ParsedMin aggregation = search.getAggregations().get("min_grade");
    System.out.println(aggregation.getValue());
}

4.3 Max

獲取聚合數據的最大值:

POST /test4/_search
{
  "aggs": {
    "max_grade": {
      "max": {
        "field": "grade"
      }
    }
  }
}

@Test
public void testMax() throws Exception {
    //封裝了獲取RestHighLevelClient的方法
    RestHighLevelClient client=ElasticSearchClient.getClient();
    SearchRequest request = new SearchRequest("test4");
    SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
    searchSourceBuilder.aggregation(AggregationBuilders.max("max_grade").field("grade")).size(0);
    request.source(searchSourceBuilder);
    SearchResponse search = client.search(request, RequestOptions.DEFAULT);
    ParsedMax aggregation = search.getAggregations().get("max_grade");
    System.out.println(aggregation.getValue());
}

4.4 Sum

獲取聚合數據的和:

POST /test4/_search
{
  "aggs": {
    "sum_grade": {
      "sum": {
        "field": "grade"
      }
    }
  }
}

@Test
public void testSum() throws Exception {
    //封裝了獲取RestHighLevelClient的方法
    RestHighLevelClient client=ElasticSearchClient.getClient();
    SearchRequest request = new SearchRequest("test4");
    SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
    searchSourceBuilder.aggregation(AggregationBuilders.sum("sum_grade").field("grade")).size(0);
    request.source(searchSourceBuilder);
    SearchResponse search = client.search(request, RequestOptions.DEFAULT);
    ParsedSum aggregation = search.getAggregations().get("sum_grade");
    System.out.println(aggregation.getValue());
}

4.5 Stats

stats集成了上面的所有計算操作。

POST /test4/_search
{
  "aggs": {
    "stats_grade": {
      "stats": {
        "field": "grade"
      }
    }
  }
}

@Test
public void testStats() throws Exception {
    //封裝了獲取RestHighLevelClient的方法
    RestHighLevelClient client=ElasticSearchClient.getClient();
    SearchRequest request = new SearchRequest("test4");
    SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
    searchSourceBuilder.aggregation(AggregationBuilders.stats("sum_grade").field("grade")).size(0);
    request.source(searchSourceBuilder);
    SearchResponse search = client.search(request, RequestOptions.DEFAULT);
    ParsedStats aggregation = search.getAggregations().get("sum_grade");
    System.out.println(aggregation.getMax());
    System.out.println(aggregation.getAvg());
    System.out.println(aggregation.getCount());
    System.out.println(aggregation.getMin());
    System.out.println(aggregation.getSum());
}

(五)Bucket Aggregation

桶聚合是按照某個欄位將同類型的數據聚合為一類,最常用對桶聚合就是terms聚合了。

5.1 terms

terms查詢類似於group by,返回查詢欄位分組後的值以及數量,比如我對classroom欄位terms查詢

POST /test4/_search
{
  "aggs": {
    "classroom_term": {
      "terms": {
        "field": "classroom"
      }
    }
  }
}

返回值就是classroom的分組後的值以及每個組的數量:classroom是1的有3條記錄,classroom是2的有2條記錄

"aggregations" : {
    "classroom_term" : {
      "doc_count_error_upper_bound" : 0,
      "sum_other_doc_count" : 0,
      "buckets" : [
        {
          "key" : "1",
          "doc_count" : 3
        },
        {
          "key" : "2",
          "doc_count" : 2
        }
      ]
    }

我們也可以對多個欄位進行terms分組,比如我現在對classroom和gender兩個欄位進行分組:

POST /test4/_search
{
  "aggs": {
    "classroom_term": {
      "terms": {
        "field": "classroom"
      },
      "aggs": {
        "gender": {
          "terms": {
            "field": "gender"
          }
        }
      }
    }
  }
}

最後對返回值就是classroom和gender分組後的值和數量:

classroom是1,gender是男有兩條;

classroom是1,gender是女有一條;

classroom是2,gender是男有一條;

classroom是2,gender是女有一條;

"aggregations" : {
    "classroom_term" : {
      "doc_count_error_upper_bound" : 0,
      "sum_other_doc_count" : 0,
      "buckets" : [
        {
          "key" : "1",
          "doc_count" : 3,
          "gender" : {
            "doc_count_error_upper_bound" : 0,
            "sum_other_doc_count" : 0,
            "buckets" : [
              {
                "key" : "男",
                "doc_count" : 2
              },
              {
                "key" : "女",
                "doc_count" : 1
              }
            ]
          }
        },
        {
          "key" : "2",
          "doc_count" : 2,
          "gender" : {
            "doc_count_error_upper_bound" : 0,
            "sum_other_doc_count" : 0,
            "buckets" : [
              {
                "key" : "女",
                "doc_count" : 1
              },
              {
                "key" : "男",
                "doc_count" : 1
              }
            ]
          }
        }
      ]
    }

對應的JavaApi使用如下:

@Test
public void testTerms() throws Exception {
    //封裝了獲取RestHighLevelClient的方法
    RestHighLevelClient client=ElasticSearchClient.getClient();
    SearchRequest request = new SearchRequest("test4");
    SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
    searchSourceBuilder.aggregation(AggregationBuilders.terms("classroom_term").field("classroom")
                        .subAggregation(AggregationBuilders.terms("gender").field("gender")));
    request.source(searchSourceBuilder);
    SearchResponse search = client.search(request, RequestOptions.DEFAULT);
    //獲取數據時首先對classroom分桶,再對gender分桶
    Terms classroomTerm = search.getAggregations().get("classroom_term");
    for(Terms.Bucket classroomBucket:classroomTerm.getBuckets()){
        Terms genderTerm=classroomBucket.getAggregations().get("gender");
        for (Terms.Bucket genderBucket:genderTerm.getBuckets()){
            System.out.println("classRoom:"+classroomBucket.getKeyAsString()+"gender:"+genderBucket.getKeyAsString()+"count:"+genderBucket.getDocCount());
        }
    }
}

這裡比較難理解對是獲取數據時的處理,聚合查詢時有個桶的概念,在獲取數據時需要遍歷獲取桶,以上面的代碼為例,先獲取到classroom的桶,再遍歷classroom的桶獲取gender的桶,從桶中獲取到具體的內容。看下圖:

5.2 range

range查詢可以統計出每個數據區間內的數量:比如我要統計分數為*~70,70~85,80~*的數據,就可以通過下面的方式:

POST /test4/_search
{
  "aggs": {
    "grade_range": {
      "range": {
        "field": "grade",
        "ranges": [
          {"to":70},
          {"from":70,"to":85},
          {"from":85}
        ]
      }
    }
  }
}

JavaAPI如下:

@Test
public void testRange() throws Exception {
    //封裝了獲取RestHighLevelClient的方法
    RestHighLevelClient client=ElasticSearchClient.getClient();
    SearchRequest request = new SearchRequest("test4");
    SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
    searchSourceBuilder.aggregation(AggregationBuilders.range("grade_range").field("grade")
                                    .addUnboundedTo(70).addRange(70,85).addUnboundedFrom(85));
    request.source(searchSourceBuilder);
    SearchResponse search = client.search(request, RequestOptions.DEFAULT);
    //獲取數據時首先對classroom分桶,再對gender分桶
    Range gradeRange = search.getAggregations().get("grade_range");
    for(Range.Bucket gradeBucket:gradeRange.getBuckets()){
        System.out.println("key:"+gradeBucket.getKey()+"count:"+gradeBucket.getDocCount());
    }
}

(六)總結

至此,關於ES的聚合查詢一些常用方法就講解完畢了,ES提供的其他更多方法可以直接在官方文檔中看,講解的十分詳細。我是魚仔,我們下期再見!

相關焦點

  • ElasticSearch聚合實戰+優化
    一個雙層聚合的例子 ( https://www.elastic.co/guide/cn/elasticsearch/guide/current/_extended_example.html ):如果有了具體的聚合結果,生成報表就方便多了,我們可以對不同的聚合結果用不同的樣式展示,如上圖的柱狀圖和折線圖, 用類似 Kibana 和 Grafana 這種工具很方便生成。
  • Elasticsearch(三):實戰
    另一種為request body search,基於json的查詢語句,俗稱dsl語句。和get請求類似,在url請求url中加查詢條件,如示例:GET /索引名稱/_search?q=createdOrgName:總公司。
  • SpringBoot 操作 ElasticSearch 詳解
    >一、ElasticSearch 簡介1、簡介ElasticSearch 是一個基於 Lucene 的搜索伺服器。主要原因是靈活性和更新速度,Spring 將 ElasticSearch 過度封裝,讓開發者很難跟 ES 的 DSL 查詢語句進行關聯。再者就是更新速度,ES 的更新速度是非常快,但是 spring-data-elasticsearch 更新速度比較緩慢。
  • elasticsearch搜索語法梳理 · 壹
    前言elasticsearch的核心在搜索,搜索的核心在搜索語法,所以今天我們來梳理下elasticsearch的一些搜索語法,今天主要探討搜索,主要包括兩方面的內容,一方面是普通的query,也就是數據的檢索,另一方面就是內容的聚合,也就是傳統sql中的分組。
  • Elasticsearch SQL用法詳解
    /bin/elasticsearch-plugin install file:/elasticsearch-sql-5.6.3.0.zip3.重啟ES服務 執行完上述三步,你就可以使用SQL探索數據了,以kibana中的使用為例:二、6.4 Elasticsearch SQL用法
  • go-ElasticSearch入門看這一篇就夠了(一)
    初識ElasticSearch ElasticSearch是一個分布式、RESTful風格的搜索和數據分析引擎,在國內簡稱為ES;使用Java開發的,底層基於Lucene是一種全文檢索的搜索庫,直接使用使用Lucene還是比較麻煩的,Elasticsearch在Lucene的基礎上開發了一個強大的搜尋引擎。
  • Elasticsearch高級調優方法論之——根治慢查詢!
    )https://www.elastic.co/guide/en/elasticsearch/guide/master/capacity-planning.html2.2 症狀2:線程池存在大量rejected搜索線程池顯示「拒絕」計數的持續增加,該計數基於上次群集重新啟動而累積。
  • SpringBoot 操作 ElasticSearch 詳解(萬字長文)
    它提供了一個分布式多員工能力的全文搜尋引擎,基於 RESTful web 接口。Elasticsearch 是用 Java 語言開發的,並作為 Apache 許可條款下的開放源碼發布,是一種流行的企業級搜尋引擎。ElasticSearch 用於雲計算中,能夠達到實時搜索,穩定,可靠,快速,安裝使用方便。
  • 走進大數據丨ElasticSearch6.X的JavaAPI使用一
    通過Java操作Elastic Search6,環境請看:走進大數據丨 ElasticSearch6.X的JavaAPI環境部署import org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequest;import org.elasticsearch.action.get.GetResponse
  • Elasticsearch快速入門,掌握這些剛剛好!
    簡介 Elasticsearch是一個基於Lucene的搜索伺服器。它提供了一個分布式的全文搜尋引擎,基於restful web接口。Elasticsearch是用Java語言開發的,基於Apache協議的開源項目,是目前最受歡迎的企業搜尋引擎。Elasticsearch廣泛運用於雲計算中,能夠達到實時搜索,具有穩定,可靠,快速的特點。
  • 網際網路公司Elasticsearch應用案例分享
    一、京東到家訂單中心 Elasticsearch 演進歷程三、去哪兒:訂單中心基於elasticsearch 的解決方案四、Elasticsearch 在58集團信息安全部的應用國內現在有大量的公司都在使用 Elasticsearch,包括攜程、滴滴、今日頭條、餓了麼、360安全、小米、vivo等諸多知名公司。
  • Elasticsearch 檢索性能優化實戰指南
    4、CPU 考慮核數和線程數在並發寫入或查詢量大之後,就會出現 CPU 打滿的情況。可以優化的空間就是:基於CPU 核數合理調節線程池和隊列的大小。6、儘可能減少檢索欄位數目query_string 或 multi_match 查詢目標的欄位越多,速度就越慢。
  • 記錄一下Elasticsearch+Filebeat+Kibana搭建過程(單節點)
    +Filebeat+Kibana)ElasticSearch(https://www.elastic.co/cn/elasticsearch/) 是一個基於Lucene的開源分布式搜索伺服器。它的特點有:分布式,零配置,自動發現,索引自動分片,索引副本機制,restful風格接口,多數據源,自動搜索負載等。它提供了一個分布式多用戶能力的全文搜尋引擎,基於RESTful web接口。Elasticsearch是用Java開發的,並作為Apache許可條款下的開放源碼發布,是第二流行的企業搜尋引擎。設計用於雲計算中,能夠達到實時搜索,穩定,可靠,快速,安裝使用方便。
  • ElasticSearch
    >3.5 索引操作總結4 操作文檔4.1 添加/修改文檔4.2 查詢文檔4.3 刪除文檔4.4 文檔操作總結5 數據批量操作5.1 Bulk批量操作5.2 批量導入6 ES查詢6.1 matchAllQuery-- 查詢所有6.2 詞條查詢
  • 在elasticsearch中使用function_score查詢
    前端時間一直在研究kubernets+.net core相關,本打算寫幾篇k8s的文章,但在公司聽到同事遇到個需求一直沒有搞定,於是出於程式設計師的本能了解了一下,就是需要根據左上角,右下角的經緯度坐標查詢一批在地圖上均勻分布!
  • 深入淺出 spring-data-elasticsearch - 實戰案例詳解(四)
    工程代碼詳解 一、搜索實戰場景需求搜索的場景會很多,常用的搜索場景,需要搜索的欄位很多,但每個欄位匹配到後所佔的權重又不同。比如電商網站的搜索,搜到商品名稱和商品描述,自然商品名稱的權重遠遠大於商品描述。而且單詞匹配肯定不如短語匹配。這樣就出現了新的需求,如何確定這些短語,即自然分詞。那就利用分詞器,即可得到所需要的短語,然後進行搜索。下面介紹短語如何進行按權重分匹配搜索。二、運行 spring-data-elasticsearch-query 工程1.
  • 深入淺出 spring-data-elasticsearch - 基本案例詳解(三)
    工程三、spring-data-elasticsearch-crud 工程代碼詳解一、spring-data-elasticsearch-crud 的工程介紹spring-data-elasticsearch-crud 的工程,介紹 Spring Data Elasticsearch 簡單的 ES 操作。
  • Elasticsearch官方已支持SQL查詢,用起來賊方便!
    平時使用Elasticsearch的時候,偶爾會在Kibana中使用Query DSL來查詢數據。每次要用到Query DSL時都基本忘光了,需要重新在回顧一遍,最近發現Elasticsearch已經支持SQL查詢了(6.3版本以後),整理了下其用法,希望對大家有所幫助!
  • 乾貨 | 通透理解Elasticsearch聚合
    本文基於官方文檔,梳理出聚合的以下幾個核心問題,目的:將Elasticsearch的聚合結合實際場景說透。1、Elasticsearch聚合最直觀展示區別於倒排索引的key value的全文檢索,聚合兩個示例如下:如下圖,是基於某特定分類的聚合統計結果。如下圖:是基於月份的聚合統計結果。
  • ElasticSearch入門 附.NET Core例子
    Elasticsearch是基於Lucene的搜尋引擎。它提供了一個分布式,支持多租戶的全文搜尋引擎,它具有HTTP Web界面和無模式JSON文檔。Elasticsearch是用Java開發的,根據Apache許可條款作為開源發布。