主頁 >  其他 > SpringBoot整合Elasticsearch之索引,映射,檔案,搜索的基本操作案例分析

SpringBoot整合Elasticsearch之索引,映射,檔案,搜索的基本操作案例分析

2022-02-06 07:34:48 其他

索引,映射,檔案,DSL增刪改查

  • 一)環境準備
    • 1. ES版本:7.12.1
    • 2. SpringBoot版本:2.5.8
  • 二)ES的基本介紹
    • 1. Elasticsearch 是什么
    • 2. Eelasticsearch的作用
    • 3. Elasticsearch,Solr和Lucene三者之間的關系
    • 4. Elasticsearch的索引結構--倒排索引
    • 5. ES中的一些基本概念
  • 三)ES索引的增刪改查
    • 1. mapping映射屬性
    • 2. 索引庫的創建
    • 3. 查詢索引庫
    • 4. 修改索引庫
    • 5. 洗掉索引庫
    • 6. 打開關閉索引庫
  • 四)ES檔案的增刪改查
    • 1. 創建檔案
    • 2. 查詢檔案
    • 3. 洗掉檔案
    • 4. 修改檔案
    • 5. 批量增刪改檔案
    • 6. SpringData版增刪改查
  • 五)ES的高級查詢
    • 1. DSL查詢分類
    • 2. 查詢所有檔案
    • 3. 全文檢索查詢
    • 4. 關鍵字精確查詢
    • 5. 指定查詢欄位
    • 6. 地理坐標查詢
    • 7. 算分函式查詢
    • 8. 模糊查詢
    • 9. 復合查詢
    • 10. 排序
    • 11. 分頁
    • 12. 高亮查詢
    • 13. 聚合查詢
  • 六) 聚合案例
    • 需求1 統計哪種顏色的電視銷量最高
    • 需求2 統計每種顏色電視平均價格
    • 需求3 繼續下鉆分析
    • 需求4:更多的metric
    • 需求5:劃分范圍 histogram
    • 需求6:按照日期分組聚合
    • 需求7 統計每季度每個品牌的銷售額
    • 需求8 :搜索與聚合結合,查詢某個品牌按顏色銷量
    • 需求9 global bucket:單個品牌與所有品牌銷量對比
    • 需求10:過濾+聚合:統計價格大于1200的電視平均價格
    • 需求11 bucket filter:統計品牌最近一個月的平均價格
    • 需求12 排序:按每種顏色的平均銷售額降序排序
    • 需求13 排序:按每種顏色的每種品牌平均銷售額降序排序

一)環境準備

1. ES版本:7.12.1

2. SpringBoot版本:2.5.8

 <parent>
        <artifactId>spring-boot-parent</artifactId>
        <groupId>org.springframework.boot</groupId>
        <version>2.5.8</version>
    </parent>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
    </dependencies>

二)ES的基本介紹

1. Elasticsearch 是什么

Elaticsearch,簡稱為 ES,ES 是一個開源的高擴展的分布式全文搜索引擎,是整個 Elastic Stack 技術堆疊的核心,它可以近乎實時的存盤、檢索資料;本身擴展性很好,可以擴展到上百臺服務器,處理 PB 級別的資料,
在這里插入圖片描述
The Elastic Stack, 包括 Elasticsearch、Kibana、Beats 和 Logstash(也稱為 ELK Stack),能夠安全可靠地獲取任何來源、任何格式的資料,然后實時地對資料進行搜索、分析和可視化,

2. Eelasticsearch的作用

Elasticsearch是一款非常強大的開源搜索引擎,具備非常多強大功能,可以幫助我們從海量資料中快速找到需要的內容,

  • 在GitHub搜索代碼
  • 用于搜索引擎中搜索內容
  • 各大電商網站搜索商品
  • 打車軟體搜索附近的車輛

3. Elasticsearch,Solr和Lucene三者之間的關系

目前市面上流行的搜索引擎軟體,主流的就兩款:Elasticsearch 和 Solr,這兩款都是基于 Lucene 搭建的,可以獨立部署啟動的搜索引擎服務軟體,

Lucene是一個Java語言的搜索引擎類別庫,是Apache公司的頂級專案,提供了一個簡單卻強大的應用程式介面,能夠做全文索引和搜尋,

Elasticsearch和Solr對比

特征Solr/SolrCloudElasticsearch
社區和開發者Apache軟體基金和社區支持單一商業物體及其員工
節點發現Apache Zookeeper.在大量專案中成熟且經過實戰測驗Zen內置于Elasticsearch本身,需要專用的主節點才能進行裂腦保護
碎片放置本質上是靜態,需要手動作業來遷移分片,從Solr 7開始- AutoscalingAPI允許一些動態操作動態,可以根據群集狀態按需移動分片
高速快取全域,每個段更改無效每段,更適合動態更改資料
分析引擎性能非常適合精確計算的靜態資料結果的準確性取決于資料放置
全文搜索功能基于Lucene的語言分析,多建議,拼寫檢查,豐富的高亮顯示支持基于Lucene的語言分析,單一建議API實作, 高亮顯示重新計算
DevOps支持尚未完全,但即將到來非常好的API
非平面資料處理嵌套檔案和父子支持嵌套和物件型別的自然支持允許幾乎無限的嵌套和父-子支持
查詢DSLJSON (有限),XML (有限)或URL引數JSON
機器學習內置-在流聚合之上,專注于邏輯回歸和學習排名貢獻模塊商業功能,專注于例外和例外值以及時間序列資料

4. Elasticsearch的索引結構–倒排索引

倒排索引的概念是基于MySQL這樣的正向索引而言的,

正向索引

那么什么是正向索引呢?例如給下表(tb_goods)中的id創建索引:
在這里插入圖片描述
如果是根據id查詢,那么直接走索引,查詢速度非常快,

但如果是基于title做模糊查詢,只能是逐行掃描資料,流程如下:

1)用戶搜索資料,條件是title符合"%手機%"

2)逐行獲取資料,比如id為1的資料

3)判斷資料中的title是否符合用戶搜索條件

4)如果符合則放入結果集,不符合則丟棄,回到步驟1

逐行掃描,也就是全表掃描,隨著資料量增加,其查詢效率也會越來越低,當資料量達到數百萬時,就是一場災難,

倒排索引

倒排索引中有兩個非常重要的概念:

  • 檔案(Document):用來搜索的資料,其中的每一條資料就是一個檔案,例如一個網頁、一個商品資訊,
  • 詞條(Term):對檔案資料或用戶搜索資料,利用某種演算法分詞,得到的具備含義的詞語就是詞條,例如:我是中國人,就可以分為:我、是、中國人、中國、國人這樣的幾個詞條

創建倒排索引是對正向索引的一種特殊處理,流程如下:

  • 將每一個檔案的資料利用演算法分詞,得到一個個詞條
  • 創建表,每行資料包括詞條、詞條所在檔案id、位置等資訊
  • 因為詞條唯一性,可以給詞條創建索引,例如hash表結構索引

如圖:
在這里插入圖片描述
倒排索引的搜索流程如下(以搜索"華為手機"為例):

1)用戶輸入條件"華為手機"進行搜索,

2)對用戶輸入內容分詞,得到詞條:華為手機

3)拿著詞條在倒排索引中查找,可以得到包含詞條的檔案id:1、2、3,

4)拿著檔案id到正向索引中查找具體檔案,

如圖:
在這里插入圖片描述
雖然要先查詢倒排索引,再查詢倒排索引,但是無論是詞條、還是檔案id都建立了索引,查詢速度非常快!無需全表掃描,

正向索引和倒排索引比較

  • 正向索引是最傳統的,根據id索引的方式,但根據詞條查詢時,必須先逐潭訓取每個檔案,然后判斷檔案中是否包含所需要的詞條,是根據檔案找詞條的程序

  • 倒排索引則相反,是先找到用戶要搜索的詞條,根據詞條得到保護詞條的檔案的id,然后根據id獲取檔案,是根據詞條找檔案的程序

正向索引

  • 優點:
    • 可以給多個欄位創建索引
    • 根據索引欄位搜索、排序速度非常快
  • 缺點:
    • 根據非索引欄位,或者索引欄位中的部分詞條查找時,只能全表掃描,

倒排索引

  • 優點:
    • 根據詞條搜索、模糊搜索時,速度非常快
  • 缺點:
    • 只能給詞條創建索引,而不是欄位
    • 無法根據欄位做排序

5. ES中的一些基本概念

elasticsearch中有很多獨有的概念,與mysql中略有差別,但也有相似之處,

結點和集群

結點(Node):每個es實體稱為一個節點,節點名自動分配,也可以手動配置,

集群(cluster):包含一個或多個啟動著es實體的機器群,通常一臺機器起一個es實體,同一網路下,集名一樣的多個es實體自動組成集群,自動均衡分片等行為,默認集群名為“elasticsearch”,

分片和副本

分片 ( shard ): index資料過大時,將index里面的資料,分為多個shard,分布式的存盤在各個服務器上面,可以支持海量資料和高并發,提升性能和吞吐量,充分利用多臺機器的cpu,

副本( replica ) : 在分布式環境下,任何一臺機器都會隨時宕機,如果宕機,index的一個分片沒有,導致此index不能搜索,所以,為了保證資料的安全,我們會將每個index的分片經行備份,存盤在另外的機器上,保證少數機器宕機es集群仍可以搜索,

能正常提供查詢和插入的分片我們叫做主分片(primary shard),其余的我們就管他們叫做備份的分片(replica shard),

檔案和欄位

elasticsearch是面向檔案(Document) 存盤的,可以是資料庫中的一條商品資料,一個訂單資訊,檔案資料會被序列化為json格式后存盤在elasticsearch中:
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-4JWzZNSz-1633240596343)(assets/image-20210720202707797.png)]

而Json檔案中往往包含很多的欄位(Field),類似于資料庫中的列,

索引和映射

索引(Index),就是相同型別的檔案的集合,

例如:

  • 所有用戶檔案,就可以組織在一起,稱為用戶的索引;
  • 所有商品的檔案,可以組織在一起,稱為商品的索引;
  • 所有訂單的檔案,可以組織在一起,稱為訂單的索引;
    在這里插入圖片描述

因此,我們可以把索引當做是資料庫中的表,

資料庫的表會有約束資訊,用來定義表的結構、欄位的名稱、型別等資訊,因此,索引庫中就有映射(mapping),是索引中檔案的欄位約束資訊,類似表的結構約束,

mysql與elasticsearch比較

我們統一的把mysql與elasticsearch的概念做一下對比:

MySQLElasticsearch說明
TableIndex索引(index),就是檔案的集合,類似資料庫的表(table)
RowDocument檔案(Document),就是一條條的資料,類似資料庫中的行(Row),檔案都是JSON格式
ColumnField欄位(Field),就是JSON檔案中的欄位,類似資料庫中的列(Column)
SchemaMappingMapping(映射)是索引中檔案的約束,例如欄位型別約束,類似資料庫的表結構(Schema)
SQLDSLDSL是elasticsearch提供的JSON風格的請求陳述句,用來操作elasticsearch,實作CRUD

是不是說,我們學習了elasticsearch就不再需要mysql了呢?

并不是如此,兩者各自有自己的擅長支出:

  • Mysql:擅長事務型別操作,可以確保資料的安全和一致性

  • Elasticsearch:擅長海量資料的搜索、分析、計算

因此在企業中,往往是兩者結合使用:

  • 對安全性要求較高的寫操作,使用mysql實作
  • 對查詢性能要求較高的搜索需求,使用elasticsearch實作
  • 兩者再基于某種方式,實作資料的同步,保證一致性
    在這里插入圖片描述

資料同步思路分析

常見的資料同步方案有三種:

  • 同步呼叫
  • 異步通知
  • 監聽binlog

1.同步呼叫

方案一:同步呼叫
在這里插入圖片描述
基本步驟如下:

  • hotel-demo對外提供介面,用來修改elasticsearch中的資料
  • 酒店管理服務在完成資料庫操作后,直接呼叫hotel-demo提供的介面,

2.異步通知

方案二:異步通知

在這里插入圖片描述
流程如下:

  • hotel-admin對mysql資料庫資料完成增、刪、改后,發送MQ訊息
  • hotel-demo監聽MQ,接收到訊息后完成elasticsearch資料修改

3.監聽binlog

方案三:監聽binlog

在這里插入圖片描述

流程如下:

  • 給mysql開啟binlog功能
  • mysql完成增、刪、改操作都會記錄在binlog中
  • hotel-demo基于canal監聽binlog變化,實時更新elasticsearch中的內容

4.選擇

方式一:同步呼叫

  • 優點:實作簡單,粗暴
  • 缺點:業務耦合度高

方式二:異步通知

  • 優點:低耦合,實作難度一般
  • 缺點:依賴mq的可靠性

方式三:監聽binlog

  • 優點:完全解除服務間耦合
  • 缺點:開啟binlog增加資料庫負擔、實作復雜度高

三)ES索引的增刪改查

索引庫就類似資料庫表,mapping映射就類似表的結構,我們要向es中存盤資料,必須先創建“庫”和“表”,

1. mapping映射屬性

mapping是對索引庫中檔案的約束,常見的mapping屬性包括:

  • type:欄位資料型別,常見的簡單型別有:
    • 字串:text(可分詞的文本)、keyword(精確值,例如:品牌、國家、ip地址)
    • 數值:long、integer、short、byte、double、float、
    • 布爾:boolean
    • 日期:date
    • 物件:object
  • index:是否創建索引,默認為true
  • analyzer:使用哪種分詞器
  • store:是否將資料進行獨立存盤,默認為 false
    原始的文本會存盤在_source 里面,默認情況下其他提取出來的欄位都不是獨立存盤的,是從_source 里面提取出來的,當然你也可以獨立的存盤某個欄位,只要設定"store": true 即可,獲取獨立存盤的欄位要比從_source 中決議快得多,但是也會占用更多的空間,所以要根據實際業務需求來設定,
  • properties:該欄位的子欄位

2. 索引庫的創建

基本語法:

  • 請求方式:PUT
  • 請求路徑:/索引庫名,可以自定義
  • 請求引數:mapping映射

格式:

PUT /索引庫名稱
{
  "mappings": {
    "properties": {
      "欄位名":{
        "type": "text",
        "analyzer": "ik_smart"
      },
      "欄位名2":{
        "type": "keyword",
        "index": "false"
      },
      "欄位名3":{
        "properties": {
          "子欄位": {
            "type": "keyword"
          }
        }
      },
      // ...略
    }
  }
}

示例:

PUT /xianyu
{
  "mappings": {
    "properties": {
      "info":{
        "type": "text",
        "analyzer": "ik_smart"
      },
      "email":{
        "type": "keyword",
        "index": "falsae"
      },
      "name":{
        "properties": {
          "firstName": {
            "type": "keyword"
          }
        }
      },
      // ... 略
    }
  }
}

RestAPI基本步驟:

//1.創建請求
CreateIndexRequest request=new CreateIndexRequest("hotel");
//2.準備請求引數
request.source(HotelConstants.MAPPING_TEMPLATE, XContentType.JSON);
//3.發送請求
client.indices().create(request, RequestOptions.DEFAULT);

1.同步創建:

    //創建索引物件
    CreateIndexRequest createIndexRequest = new CreateIndexRequest("itheima_book");
    //設定引數
    createIndexRequest.settings(Settings.builder().put("number_of_shards", "1").put("number_of_replicas", "0"));
    //指定映射1
    createIndexRequest.mapping(" {\n" +
            " \t\"properties\": {\n" +
            "            \"name\":{\n" +
            "             \"type\":\"keyword\"\n" +
            "           },\n" +
            "           \"description\": {\n" +
            "              \"type\": \"text\"\n" +
            "           },\n" +
            "            \"price\":{\n" +
            "             \"type\":\"long\"\n" +
            "           },\n" +
            "           \"pic\":{\n" +
            "             \"type\":\"text\",\n" +
            "             \"index\":false\n" +
            "           }\n" +
            " \t}\n" +
            "}", XContentType.JSON);

    //指定映射2
?```

//        Map<String, Object> message = new HashMap<>();
//        message.put("type", "text");
//        Map<String, Object> properties = new HashMap<>();
//        properties.put("message", message);
//        Map<String, Object> mapping = new HashMap<>();
//        mapping.put("properties", properties);
//        createIndexRequest.mapping(mapping);

?```
    //指定映射3
?```

//        XContentBuilder builder = XContentFactory.jsonBuilder();
//        builder.startObject();
//        {
//            builder.startObject("properties");
//            {
//                builder.startObject("message");
//                {
//                    builder.field("type", "text");
//                }
//                builder.endObject();
//            }
//            builder.endObject();
//        }
//        builder.endObject();
//        createIndexRequest.mapping(builder);

?```
    //設定別名
    createIndexRequest.alias(new Alias("itheima_index_new"));

    // 額外引數
    //設定超時時間
    createIndexRequest.setTimeout(TimeValue.timeValueMinutes(2));
    //設定主節點超時時間
    createIndexRequest.setMasterTimeout(TimeValue.timeValueMinutes(1));
    //在創建索引API回傳回應之前等待的活動分片副本的數量,以int形式表示
    createIndexRequest.waitForActiveShards(ActiveShardCount.from(2));
    createIndexRequest.waitForActiveShards(ActiveShardCount.DEFAULT);

    //操作索引的客戶端
    IndicesClient indices = client.indices();
    //執行創建索引庫
    CreateIndexResponse createIndexResponse = indices.create(createIndexRequest, RequestOptions.DEFAULT);

    //得到回應(全部)
    boolean acknowledged = createIndexResponse.isAcknowledged();
    //得到回應 指示是否在超時前為索引中的每個分片啟動了所需數量的碎片副本
    boolean shardsAcknowledged = createIndexResponse.isShardsAcknowledged();

    System.out.println("!!!!!!!!!!!!!!!!!!!!!!!!!!!" + acknowledged);
    System.out.println(shardsAcknowledged);

}

2.異步創建:

 //創建索引物件
    CreateIndexRequest createIndexRequest = new CreateIndexRequest("itheima_book2");
    //設定引數
    createIndexRequest.settings(Settings.builder().put("number_of_shards", "1").put("number_of_replicas", "0"));
    //指定映射1
    createIndexRequest.mapping(" {\n" +
            " \t\"properties\": {\n" +
            "            \"name\":{\n" +
            "             \"type\":\"keyword\"\n" +
            "           },\n" +
            "           \"description\": {\n" +
            "              \"type\": \"text\"\n" +
            "           },\n" +
            "            \"price\":{\n" +
            "             \"type\":\"long\"\n" +
            "           },\n" +
            "           \"pic\":{\n" +
            "             \"type\":\"text\",\n" +
            "             \"index\":false\n" +
            "           }\n" +
            " \t}\n" +
            "}", XContentType.JSON);

    //監聽方法
    ActionListener<CreateIndexResponse> listener =
            new ActionListener<CreateIndexResponse>() {

                @Override
                public void onResponse(CreateIndexResponse createIndexResponse) {
                    System.out.println("!!!!!!!!創建索引成功");
                    System.out.println(createIndexResponse.toString());
                }

                @Override
                public void onFailure(Exception e) {
                    System.out.println("!!!!!!!!創建索引失敗");
                    e.printStackTrace();
                }
            };

    //操作索引的客戶端
    IndicesClient indices = client.indices();
    //執行創建索引庫
    indices.createAsync(createIndexRequest, RequestOptions.DEFAULT, listener);

    try {
        Thread.sleep(5000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }

3.SpringData自動創建

// 可以通過注解@Document @Filed @Setting 來自定義配置
@Document(indexName = "book")
@Data
public class Book {
    // 必須有 id,這里的 id 是全域唯一的標識,等同于 es 中的"_id"
    @Id
    private String id;
    @Field(type = FieldType.Keyword, analyzer = "ik_max_word",searchAnalyzer= "ik_smart")
    private String bookName;
    @Field(type = FieldType.Text, analyzer = "ik_max_word",searchAnalyzer= "ik_smart")
    private String bookDesc;
    @Field(type = FieldType.Double, index = false)
    private Double bookPrice;
    @Field(type = FieldType.Long, index = false)
    private Integer bookNumber;
}

3. 查詢索引庫

基本語法

  • 請求方式:GET

  • 請求路徑:/索引庫名

  • 請求引數:無

格式

GET /索引庫名

示例

GET /xianyu
{
    "xianyu"【索引名】: {
        "aliases"【別名】: {},
        "mappings"【映射】: {},
         "settings"【設定】: {
            "index"【設定 - 索引】: {
                "creation_date"【設定 - 索引 - 創建時間】: "1614265373911",
                "number_of_shards"【設定 - 索引 - 主分片數量】: "1",
                "number_of_replicas"【設定 - 索引 - 副分片數量】: "1",
                "uuid"【設定 - 索引 - 唯一標識】: "eI5wemRERTumxGCc1bAk2A",
                "version"【設定 - 索引 - 版本】: {
                "created": "7080099"
                },
             "provided_name"【設定 - 索引 - 名稱】: "xianyu"
                }
      }
         }
}

查詢所有的索引庫

#查詢所有的索引庫
GET /_cat/indices?v

在這里插入圖片描述

表頭含義
health 當前服務器健康狀態:green(集群完整) yellow(單點正常、集群不完整)red(單點不正常)
status索引打開、關閉狀態
index索引名
uuid索引統一編號
pri主分片數量
rep副本數量
docs.count可用檔案數量
docs.deleted檔案洗掉狀態(邏輯洗掉)
store.size主分片和副分片整體占空間大小
pri.store.size主分片占空間大小

RestAPI

// 查詢索引 - 請求物件
GetIndexRequest request = new GetIndexRequest("hotel");
// 發送請求,獲取回應
GetIndexResponse response = client.indices().get(request, 
RequestOptions.DEFAULT);

4. 修改索引庫

倒排索引結構雖然不復雜,但是一旦資料結構改變(比如改變了分詞器),就需要重新創建倒排索引,這簡直是災難,因此索引庫一旦創建,無法修改mapping

雖然無法修改mapping中已有的欄位,但是卻允許添加新的欄位到mapping中,因為不會對倒排索引產生影響,

語法說明

PUT /索引庫名/_mapping
{
  "properties": {
    "新欄位名":{
      "type": "integer"
    }
  }
}

5. 洗掉索引庫

語法:

  • 請求方式:DELETE

  • 請求路徑:/索引庫名

  • 請求引數:無

格式:

DELETE /索引庫名

RestAPI:

//創建洗掉請求
DeleteIndexRequest request = new DeleteIndexRequest("hotel");     
//發送請求
client.indices().delete(request,RequestOptions.DEFAULT);

異步洗掉

 //洗掉索引物件
    DeleteIndexRequest deleteIndexRequest = new DeleteIndexRequest("itheima_book2");
    //操作索引的客戶端
    IndicesClient indices = client.indices();

    //監聽方法
    ActionListener<AcknowledgedResponse> listener =
            new ActionListener<AcknowledgedResponse>() {
                @Override
                public void onResponse(AcknowledgedResponse deleteIndexResponse) {
                    System.out.println("!!!!!!!!洗掉索引成功");
                    System.out.println(deleteIndexResponse.toString());
                }

                @Override
                public void onFailure(Exception e) {
                    System.out.println("!!!!!!!!洗掉索引失敗");
                    e.printStackTrace();
                }
            };
    //執行洗掉索引
    indices.deleteAsync(deleteIndexRequest, RequestOptions.DEFAULT, listener);

    try {
        Thread.sleep(5000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }

6. 打開關閉索引庫

 OpenIndexRequest request = new OpenIndexRequest("itheima_book");

    OpenIndexResponse openIndexResponse = client.indices().open(request, RequestOptions.DEFAULT);
    boolean acknowledged = openIndexResponse.isAcknowledged();
    System.out.println("!!!!!!!!!"+acknowledged);
 CloseIndexRequest request = new CloseIndexRequest("index");
    AcknowledgedResponse closeIndexResponse = client.indices().close(request, RequestOptions.DEFAULT);
    boolean acknowledged = closeIndexResponse.isAcknowledged();
    System.out.println("!!!!!!!!!"+acknowledged);
  GetIndexRequest request = new GetIndexRequest("itheima_book");
    request.local(false);//從主節點回傳本地資訊或檢索狀態
    request.humanReadable(true);//以適合人類的格式回傳結果
    request.includeDefaults(false);//是否回傳每個索引的所有默認設定

    boolean exists = client.indices().exists(request, RequestOptions.DEFAULT);
    System.out.println(exists);

四)ES檔案的增刪改查

1. 創建檔案

語法:

POST /索引庫名/_doc/檔案id 
{
    "欄位1": "值1",
    "欄位2": "值2",
    "欄位3": {
        "子屬性1": "值3",
        "子屬性2": "值4"
    },
    // ...
}

示例:

POST /xianyu/_doc/1 不加id會隨機生成一個id
{
  "name":"咸魚",
  "age":23
}

{
  "_index" : "xianyu", //索引名
  "_type" : "_doc", //型別
  "_id" : "1", //唯一標識 類似主鍵
  "_version" : 1, //版本
  "result" : "created", //結果 表示創建成功
  "_shards" : {  //分片
    "total" : 2,
    "successful" : 1,
    "failed" : 0
  },
  "_seq_no" : 0,
  "_primary_term" : 1
}

RESTAPI

// 1.根據id查詢酒店資料 
Hotel hotel = hotelService.getById(61083L);
// 2.轉換為檔案型別
HotelDoc hotelDoc = new HotelDoc(hotel);
// 3.將HotelDoc轉json
String jsonString = JSON.toJSONString(hotelDoc);
// 1.準備Request物件
IndexRequest request = new IndexRequest("hotel").id(hotelDoc.getId().toString());
// 2.準備Json檔案
request.source(jsonString, XContentType.JSON);
// 3.發送請求
client.index(request, RequestOptions.DEFAULT);

同步

//        1構建請求
        IndexRequest request=new IndexRequest("test_posts");
        request.id("3");
//        =======================構建檔案============================
//        構建方法1
        String jsonString="{\n" +
                "  \"user\":\"tomas J\",\n" +
                "  \"postDate\":\"2019-07-18\",\n" +
                "  \"message\":\"trying out es3\"\n" +
                "}";
        request.source(jsonString, XContentType.JSON);

//        構建方法2
//        Map<String,Object> jsonMap=new HashMap<>();
//        jsonMap.put("user", "tomas");
//        jsonMap.put("postDate", "2019-07-18");
//        jsonMap.put("message", "trying out es2");
//        request.source(jsonMap);

//        構建方法3
//        XContentBuilder builder= XContentFactory.jsonBuilder();
//        builder.startObject();
//        {
//            builder.field("user", "tomas");
//            builder.timeField("postDate", new Date());
//            builder.field("message", "trying out es2");
//        }
//        builder.endObject();
//        request.source(builder);
//        構建方法4
//        request.source("user","tomas",
//                    "postDate",new Date(),
//                "message","trying out es2");
//
//        ========================可選引數===================================
        //設定超時時間
        request.timeout(TimeValue.timeValueSeconds(1));
        request.timeout("1s");

        //自己維護版本號
//        request.version(2);
//        request.versionType(VersionType.EXTERNAL);



//        2執行
        //同步
        IndexResponse indexResponse = client.index(request, RequestOptions.DEFAULT);

異步

 //異步
//        ActionListener<IndexResponse> listener=new ActionListener<IndexResponse>() {
//            @Override
//            public void onResponse(IndexResponse indexResponse) {
//
//            }
//
//            @Override
//            public void onFailure(Exception e) {
//
//            }
//        };
//        client.indexAsync(request,RequestOptions.DEFAULT, listener );
//        try {
//            Thread.sleep(5000);
//        } catch (InterruptedException e) {
//            e.printStackTrace();
//        }


//        3獲取結果
        String index = indexResponse.getIndex();
        String id = indexResponse.getId();
        //獲取插入的型別
        if(indexResponse.getResult()== DocWriteResponse.Result.CREATED){
            DocWriteResponse.Result result=indexResponse.getResult();
            System.out.println("CREATED:"+result);
        }else if(indexResponse.getResult()== DocWriteResponse.Result.UPDATED){
            DocWriteResponse.Result result=indexResponse.getResult();
            System.out.println("UPDATED:"+result);
        }

        ReplicationResponse.ShardInfo shardInfo = indexResponse.getShardInfo();
        if(shardInfo.getTotal()!=shardInfo.getSuccessful()){
            System.out.println("處理成功的分片數少于總分片!");
        }
        if(shardInfo.getFailed()>0){
           for (ReplicationResponse.ShardInfo.Failure failure:shardInfo.getFailures()) {
               String reason = failure.reason();//處理潛在的失敗原因
               System.out.println(reason);
           }
        }
    }

2. 查詢檔案

語法:

GET /{索引庫名稱}/_doc/{id}
GET /xianyu/_doc/1

{
  "_index" : "xianyu",
  "_type" : "_doc",
  "_id" : "1",
  "_version" : 1,
  "_seq_no" : 0,
  "_primary_term" : 1,
  "found" : true,
  "_source" : {
    "name" : "咸魚",
    "age" : 23
  }
}

RESTAPI

 GetRequest request = new GetRequest("hotel","61083");
//得到回應
GetResponse response = client.get(request, RequestOptions.DEFAULT);
String json = response.getSourceAsString();
//決議檔案
HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);
  //構建請求
        GetRequest getRequest = new GetRequest("test_post", "1");

        //========================可選引數 start======================
        //為特定欄位配置_source_include
//        String[] includes = new String[]{"user", "message"};
//        String[] excludes = Strings.EMPTY_ARRAY;
//        FetchSourceContext fetchSourceContext = new FetchSourceContext(true, includes, excludes);
//        getRequest.fetchSourceContext(fetchSourceContext);

        //為特定欄位配置_source_excludes
//        String[] includes1 = new String[]{"user", "message"};
//        String[] excludes1 = Strings.EMPTY_ARRAY;
//        FetchSourceContext fetchSourceContext1 = new FetchSourceContext(true, includes1, excludes1);
//        getRequest.fetchSourceContext(fetchSourceContext1);

        //設定路由
//        getRequest.routing("routing");

        // ========================可選引數 end=====================


        //查詢 同步查詢
      GetResponse getResponse = client.get(getRequest, RequestOptions.DEFAULT);

        //異步查詢
//        ActionListener<GetResponse> listener = new ActionListener<GetResponse>() {
//            //查詢成功時的立馬執行的方法
//            @Override
//            public void onResponse(GetResponse getResponse) {
//                long version = getResponse.getVersion();
//                String sourceAsString = getResponse.getSourceAsString();//檢索檔案(String形式)
//                System.out.println(sourceAsString);
//            }
//
//            //查詢失敗時的立馬執行的方法
//            @Override
//            public void onFailure(Exception e) {
//                e.printStackTrace();
//            }
//        };
//        //執行異步請求
//        client.getAsync(getRequest, RequestOptions.DEFAULT, listener);
//        try {
//            Thread.sleep(5000);
//        } catch (InterruptedException e) {
//            e.printStackTrace();
//        }

        // 獲取結果
        if (getResponse.isExists()) {
            long version = getResponse.getVersion();

            String sourceAsString = getResponse.getSourceAsString();//檢索檔案(String形式)
            System.out.println(sourceAsString);
            byte[] sourceAsBytes = getResponse.getSourceAsBytes();//以位元組接受
            Map<String, Object> sourceAsMap = getResponse.getSourceAsMap();
            System.out.println(sourceAsMap);

        }else {

        }

    }

3. 洗掉檔案

語法:

DELETE /{索引庫名稱}/_doc/{id}
DELETE /xianyu/_doc/1

{
  "_index" : "xianyu",
  "_type" : "_doc",
  "_id" : "1",
  "_version" : 2,
  "result" : "deleted",
  "_shards" : {
    "total" : 2,
    "successful" : 1,
    "failed" : 0
  },
  "_seq_no" : 1,
  "_primary_term" : 1
}

條件洗掉檔案

POST /xianyu/_delete_by_query
{
 "query":{
 "match":{
 "age":23
 }
 }
}

RESTAPI

 // 1.準備Request
DeleteRequest request = new DeleteRequest("hotel", "61083");
// 2.發送請求
client.delete(request, RequestOptions.DEFAULT);

4. 修改檔案

修改有兩種方式:

  • 全量修改:直接覆寫原來的檔案
  • 增量修改:修改檔案中的部分欄位

語法:

PUT /{索引庫名}/_doc/檔案id
{
    "欄位1": "值1",
    "欄位2": "值2",
    // ... 略
}

增量修改是只修改指定id匹配的檔案中的部分欄位,

語法:

POST /{索引庫名}/_update/檔案id
{
    "doc": {
         "欄位名": "新的值",
    }
}
POST /xianyu/_update/1
{
  "doc": {
    "no":"20183033523"
  }
}

{
  "_index" : "xianyu",
  "_type" : "_doc",
  "_id" : "1",
  "_version" : 2,
  "result" : "updated",
  "_shards" : {
    "total" : 2,
    "successful" : 1,
    "failed" : 0
  },
  "_seq_no" : 3,
  "_primary_term" : 1
}

RESTAPI

// 1.準備Request
UpdateRequest request = new UpdateRequest("hotel", "61083");
 // 2.準備請求引數
 request.doc(
               "price", "952",
                "starName", "四鉆"
        );
 // 3.發送請求
client.update(request, RequestOptions.DEFAULT);
    1構建請求
        UpdateRequest request = new UpdateRequest("test_posts", "3");
        Map<String, Object> jsonMap = new HashMap<>();
        jsonMap.put("user", "tomas JJ");
        request.doc(jsonMap);
//===============================可選引數==========================================
        request.timeout("1s");//超時時間

        //重試次數
        request.retryOnConflict(3);

        //設定在繼續更新之前,必須激活的分片數
//        request.waitForActiveShards(2);
        //所有分片都是active狀態,才更新
//        request.waitForActiveShards(ActiveShardCount.ALL);

//        2執行
//        同步
        UpdateResponse updateResponse = client.update(request, RequestOptions.DEFAULT);
//        異步

//        3獲取資料
        updateResponse.getId();
        updateResponse.getIndex();

        //判斷結果
        if (updateResponse.getResult() == DocWriteResponse.Result.CREATED) {
            DocWriteResponse.Result result = updateResponse.getResult();
            System.out.println("CREATED:" + result);
        } else if (updateResponse.getResult() == DocWriteResponse.Result.UPDATED) {
            DocWriteResponse.Result result = updateResponse.getResult();
            System.out.println("UPDATED:" + result);
        }else if(updateResponse.getResult() == DocWriteResponse.Result.DELETED){
            DocWriteResponse.Result result = updateResponse.getResult();
            System.out.println("DELETED:" + result);
        }else if (updateResponse.getResult() == DocWriteResponse.Result.NOOP){
            //沒有操作
            DocWriteResponse.Result result = updateResponse.getResult();
            System.out.println("NOOP:" + result);
        }

5. 批量增刪改檔案

Bulk 操作解釋將檔案的增刪改查一些列操作,通過一次請求全都做完,減少網路傳輸次數,

語法:

POST /_bulk
{"action": {"metadata"}}
{"data"}

如下操作,洗掉5,新增14,修改2,

POST /_bulk
{ "delete": { "_index": "test_index",  "_id": "5" }} 
{ "create": { "_index": "test_index",  "_id": "14" }}
{ "test_field": "test14" }
{ "update": { "_index": "test_index",  "_id": "2"} }
{ "doc" : {"test_field" : "bulk test"} }
//        1創建請求
        BulkRequest request = new BulkRequest();
//        request.add(new IndexRequest("post").id("1").source(XContentType.JSON, "field", "1"));
//        request.add(new IndexRequest("post").id("2").source(XContentType.JSON, "field", "2"));

        request.add(new UpdateRequest("post","2").doc(XContentType.JSON, "field", "3"));
        request.add(new DeleteRequest("post").id("1"));

//        2執行
        BulkResponse bulkResponse = client.bulk(request, RequestOptions.DEFAULT);

        for (BulkItemResponse itemResponse : bulkResponse) {
            DocWriteResponse itemResponseResponse = itemResponse.getResponse();

            switch (itemResponse.getOpType()) {
                case INDEX:
                case CREATE:
                    IndexResponse indexResponse = (IndexResponse) itemResponseResponse;
                    indexResponse.getId();
                    System.out.println(indexResponse.getResult());
                    break;
                case UPDATE:
                    UpdateResponse updateResponse = (UpdateResponse) itemResponseResponse;
                    updateResponse.getIndex();
                    System.out.println(updateResponse.getResult());
                    break;
                case DELETE:
                    DeleteResponse deleteResponse = (DeleteResponse) itemResponseResponse;
                    System.out.println(deleteResponse.getResult());
                    break;
            }
        }

6. SpringData版增刪改查

```java
// Book : 物體類
// String : 主鍵型別
@Repository
public interface BookRepository extends ElasticsearchRepository<Book,String> {
    List<Book> findByBookNameLike(String bookName);
}

在這里插入圖片描述

五)ES的高級查詢

1. DSL查詢分類

Elasticsearch提供了基于JSON的DSL(Domain Specific Language)來定義查詢,常見的查詢型別包括:

  • 查詢所有:查詢出所有資料,一般測驗用,例如:match_all

  • 全文檢索(full text)查詢:利用分詞器對用戶輸入內容分詞,然后去倒排索引庫中匹配,例如:

    • match_query
    • multi_match_query
  • 精確查詢:根據精確詞條值查找資料,一般是查找keyword、數值、日期、boolean等型別欄位,例如:

    • ids
    • range
    • term
  • 地理(geo)查詢:根據經緯度查詢,例如:

    • geo_distance
    • geo_bounding_box
  • 復合(compound)查詢:復合查詢可以將上述各種查詢條件組合起來,合并查詢條件,例如:

    • bool
    • function_score

查詢基本語法:

GET /indexName/_search
{
  "query": {
    "查詢型別": {
      "查詢條件": "條件值"
    }
  }
}

2. 查詢所有檔案

// 查詢所有
GET /indexName/_search
{
  "query": {
    "match_all": {
    }
  }
}

在這里插入圖片描述
RestAPI

//1.創建請求
SearchRequest request = new SearchRequest("hotel");
//2.構建DSL
request.source().query(QueryBuilders.matchAllQuery());
//3.發送請求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
//4.決議回應
extractResponse(response);

結果決議:

	//將回應結果決議
    private void extractResponse(SearchResponse response) {
        SearchHits searchHits = response.getHits();
        //獲取總條數
        long total = searchHits.getTotalHits().value;
        System.out.println("檔案總條數為"+total);
        //獲取檔案陣列
        SearchHit[] hits = searchHits.getHits();

       // Arrays.stream(hits).forEach(v-> JSON.parseObject(v.getSourceAsString(),HotelDoc.class));

        for (SearchHit hit : hits) {
            // 獲取檔案source
            String json = hit.getSourceAsString();
            // 反序列化
            HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);
            log.info("酒店資料:{}",hotelDoc);
        }
    }

3. 全文檢索查詢

使用場景

全文檢索查詢的基本流程如下:

  • 對用戶搜索的內容做分詞,得到詞條
  • 根據詞條去倒排索引庫中匹配,得到檔案id
  • 根據檔案id找到檔案,回傳給用戶

比較常用的場景包括:

  • 商城的輸入框搜索
  • 百度輸入框搜索

因為是拿著詞條去匹配,因此參與搜索的欄位也必須是可分詞的text型別的欄位,

基本語法

常見的全文檢索查詢包括:

  • match查詢:單欄位查詢
  • multi_match查詢:多欄位查詢,任意一個欄位符合條件就算符合查詢條件,搜索欄位越多,對查詢性能影響越大,因此建議采用copy_to,然后單欄位查詢的方式,

match查詢語法如下:

GET /indexName/_search
{
  "query": {
    "match": {
      "FIELD": "TEXT"
    }
  }
}

#匹配查詢

GET /hotel/_search
{
  "query": {
    "match": {
      "city": "上海"
    }
  }
}

RestAPI

//創建請求
 SearchRequest request = new SearchRequest("hotel");
 //2.構建DSL
 request.source().query(QueryBuilders.matchQuery("city","上海"));
//3.發送請求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
//4.決議回應
extractResponse(response);

mulit_match語法如下:

GET /indexName/_search
{
  "query": {
    "multi_match": {
      "query": "TEXT",
      "fields": ["FIELD1", " FIELD12"]
    }
  }
}

#多欄位匹配查詢

注意:多欄位匹配性能較低一般涉及到多欄位搜索會使用copyto到一個欄位進行查詢

GET /hotel/_search
{
  "query": {
    "multi_match": {
      "query": "漢庭",
      "fields": ["name","business"]
    }
  }
}

RestAPI

  //1.創建請求
SearchRequest request = new SearchRequest("hotel");
//2.構建DSL
request.source().query(QueryBuilders.multiMatchQuery("漢庭","name","business"));
 //3.發送請求
 SearchResponse response = client.search(request, RequestOptions.DEFAULT);
//4.決議回應
extractResponse(response);

4. 關鍵字精確查詢

精確查詢一般是查找keyword、數值、日期、boolean等型別欄位,所以不會對搜索條件分詞,常見的有:

  • term:根據詞條精確值查詢
  • range:根據值的范圍查詢
  • terms:根據多個詞條精確查詢

term查詢

因為精確查詢的欄位搜是不分詞的欄位,因此查詢的條件也必須是不分詞的詞條,查詢時,用戶輸入的內容跟自動值完全匹配時才認為符合條件,如果用戶輸入的內容過多,反而搜索不到資料,

語法說明:

// term查詢
GET /indexName/_search
{
  "query": {
    "term": {
      "FIELD": {
        "value": "VALUE"
      }
    }
  }
}
#精確查詢
GET /hotel/_search
{
  "query": {
    "term": {
      "brand": {
        "value": "漢庭"
      }
    }
  }
}

RestAPI

 //1.創建請求
SearchRequest request = new SearchRequest("hotel");
 //2.構建DSL    
 request.source().query(QueryBuilders.termQuery("city","上海"));
//3.發送請求
 SearchResponse response = client.search(request, RequestOptions.DEFAULT);
//4.決議回應
extractResponse(response);

range查詢

范圍查詢,一般應用在對數值型別做范圍過濾的時候,比如做價格范圍過濾,
在這里插入圖片描述
基本語法:

// range查詢
GET /indexName/_search
{
  "query": {
    "range": {
      "FIELD": {
        "gte": 10, // 這里的gte代表大于等于,gt則代表大于
        "lte": 20 // lte代表小于等于,lt則代表小于
      }
    }
  }
}
#范圍查詢
GET /hotel/_search
{
  "query": {
    "range": {
      "price": {
        "gte": 300,
        "lte": 400
      }
    }
  }
}

RestAPI

//1.創建請求
SearchRequest request = new SearchRequest("hotel");
//2.構建DSL
request.source().query(QueryBuilders.rangeQuery("price").gt(200).lt(400));
//3.發送請求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
//4.決議回應
extractResponse(response);

terms查詢

terms 查詢和 term 查詢一樣,但它允許你指定多值進行匹配,如果這個欄位包含了指定值中的任何一個值,那么這個檔案滿足條件,類似于 mysql 的 in,

// term查詢
GET /indexName/_search
{
  "query": {
    "term": {
      "FIELD": {
        "value": ["VALUE1","VALUE2"]
      }
    }
  }
}
GET /hotel/_search
{
  "query": {
    "terms": {
      "city": [
        "上海",
        "北京"
      ]
    }
  }
}

RestAPI

 //1.創建請求
 SearchRequest request = new SearchRequest("hotel");
  //2.構建DSL
 request.source().query(QueryBuilders.termsQuery("city","北京","上海"));
 //3.發送請求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
//4.決議回應
extractResponse(response);

exist query 查詢有某些欄位值的檔案

GET /_search
{
    "query": {
        "exists": {
            "field": "join_date"
        }
    }
}

5. 指定查詢欄位

默認情況下,Elasticsearch 在搜索的結果中,會把檔案中保存在_source 的所有欄位都回傳,如果我們只想獲取其中的部分欄位,我們可以添加_source 的過濾,

#指定篩選欄位
GET /hotel/_search
{
  "_source": ["address","city"] ,
  "query": {
    "term": {
      "city": {
        "value": "上海"
      }
    }
  }
}
  • includes:來指定想要顯示的欄位
  • excludes:來指定不想要顯示的欄位
GET /hotel/_search
{
  "_source": {
    //"excludes": , ["address","city"],
    "includes":  ["brand","price"]
  },
  "query": {
    "term": {
      "city": {
        "value": "上海"
      }
    }
  }
}

RestAPI

 //1.創建請求
 SearchRequest request = new SearchRequest("hotel");
//查詢欄位過濾
String[] includes={};
String[] excludes={"brand","location"};
//2.構建DSL
 request.source().query(QueryBuilders.termQuery("city","上海")).fetchSource(includes,excludes);
//3.發送請求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
//4.決議回應
extractResponse(response);

6. 地理坐標查詢

所謂的地理坐標查詢,其實就是根據經緯度查詢,

常見的使用場景包括:

  • 攜程:搜索我附近的酒店
  • 滴滴:搜索我附近的出租車
  • 微信:搜索我附近的人

矩形范圍查詢

矩形范圍查詢,也就是geo_bounding_box查詢,查詢坐標落在某個矩形范圍的所有檔案:
在這里插入圖片描述
查詢時,需要指定矩形的左上右下兩個點的坐標,然后畫出一個矩形,落在該矩形內的都是符合條件的點,

語法如下:

// geo_bounding_box查詢
GET /indexName/_search
{
  "query": {
    "geo_bounding_box": {
      "FIELD": {
        "top_left": { // 左上點
          "lat": 31.1,
          "lon": 121.5
        },
        "bottom_right": { // 右下點
          "lat": 30.9,
          "lon": 121.7
        }
      }
    }
  }
}

附近查詢

附近查詢,也叫做距離查詢(geo_distance):查詢到指定中心點小于某個距離值的所有檔案,

換句話來說,在地圖上找一個點作為圓心,以指定距離為半徑,畫一個圓,落在圓內的坐標都算符合條件:
在這里插入圖片描述
語法說明:

// geo_distance 查詢
GET /indexName/_search
{
  "query": {
    "geo_distance": {
      "distance": "15km", // 半徑
      "FIELD": "31.21,121.5" // 圓心
    }
  }
}
#地理坐標查詢 半徑5km范圍內的
GET /hotel/_search
{
 "query": {
   "geo_distance": {
     "distance": "5km",
     "location": "31.21,121.5"
   }
 }  
}

RestAPI

		 //1.創建請求
        SearchRequest request = new SearchRequest("hotel");
        //2.構建DSL
        request.source().query(QueryBuilders
                .geoDistanceQuery("location")
                .distance("5", DistanceUnit.KILOMETERS)
                .point(31.21,121.5));
        //3.發送請求
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);
        //4.決議回應
        extractResponse(response);

7. 算分函式查詢

復合(compound)查詢:復合查詢可以將其它簡單查詢組合起來,實作更復雜的搜索邏輯,常見的有兩種:

  • fuction score:算分函式查詢,可以控制檔案相關性算分,控制檔案排名
  • bool query:布爾查詢,利用邏輯關系組合多個其它的查詢,實作復雜搜索

相關性算分

當我們利用match查詢時,檔案結果會根據與搜索詞條的關聯度打分(_score),回傳結果時按照分值降序排列,

例如,我們搜索 “虹橋如家”,結果如下:

[
  {
    "_score" : 17.850193,
    "_source" : {
      "name" : "虹橋如家酒店真不錯",
    }
  },
  {
    "_score" : 12.259849,
    "_source" : {
      "name" : "外灘如家酒店真不錯",
    }
  },
  {
    "_score" : 11.91091,
    "_source" : {
      "name" : "迪士尼如家酒店真不錯",
    }
  }
]

在elasticsearch中,早期使用的打分演算法是TF-IDF演算法,公式如下:
在這里插入圖片描述
在后來的5.1版本升級中,elasticsearch將演算法改進為BM25演算法,公式如下:
在這里插入圖片描述
TF-IDF演算法有一各缺陷,就是詞條頻率越高,檔案得分也會越高,單個詞條對檔案影響較大,而BM25則會讓單個詞條的算分有一個上限,曲線更加平滑:
在這里插入圖片描述
算分函式查詢

根據相關度打分是比較合理的需求,但合理的不一定是產品經理需要的,

以百度為例,你搜索的結果中,并不是相關度越高排名越靠前,而是誰掏的錢多排名就越靠前,

要想認為控制相關性算分,就需要利用elasticsearch中的function score 查詢了,

1)語法說明
在這里插入圖片描述
function score 查詢中包含四部分內容:

  • 原始查詢條件:query部分,基于這個條件搜索檔案,并且基于BM25演算法給檔案打分,原始算分(query score)
  • 過濾條件:filter部分,符合該條件的檔案才會重新算分
  • 算分函式:符合filter條件的檔案要根據這個函式做運算,得到的函式算分(function score),有四種函式
    • weight:函式結果是常量
    • field_value_factor:以檔案中的某個欄位值作為函式結果
    • random_score:以亂數作為函式結果
    • script_score:自定義算分函式演算法
  • 運算模式:算分函式的結果、原始查詢的相關性算分,兩者之間的運算方式,包括:
    • multiply:相乘
    • replace:用function score替換query score
    • 其它,例如:sum、avg、max、min

function score的運行流程如下:

  • 1)根據原始條件查詢搜索檔案,并且計算相關性算分,稱為原始算分(query score)
  • 2)根據過濾條件,過濾檔案
  • 3)符合過濾條件的檔案,基于算分函式運算,得到函式算分(function score)
  • 4)將原始算分(query score)和函式算分(function score)基于運算模式做運算,得到最終結果,作為相關性算分,

因此,其中的關鍵點是:

  • 過濾條件:決定哪些檔案的算分被修改
  • 算分函式:決定函式算分的演算法
  • 運算模式:決定最終算分結果
GET /hotel/_search
{
  "query": {
    "function_score": {
      "query": {  .... }, // 原始查詢,可以是任意條件
      "functions": [ // 算分函式
        {
          "filter": { // 滿足的條件,品牌必須是如家
            "term": {
              "brand": "如家"
            }
          },
          "weight": 2 // 算分權重為2
        }
      ],
      "boost_mode": "sum" // 加權模式,求和
    }
  }
}

RestAPI
在這里插入圖片描述

8. 模糊查詢

回傳包含與搜索字詞相似的字詞的檔案,

IDs

GET /book/_search
{
    "query": {
        "ids" : {
            "values" : ["1", "4", "100"]
        }
    }
}

RestAPI

 		// 1構建搜索請求
        SearchRequest searchRequest = new SearchRequest("book");

        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        searchSourceBuilder.query(QueryBuilders.idsQuery().addIds("1","4","100"));




        searchRequest.source(searchSourceBuilder);

        //2執行搜索
        SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);

        //3獲取結果
        SearchHits hits = searchResponse.getHits();

prefix 前綴查詢

GET /book/_search
{
    "query": {
        "prefix": {
            "description": {
                "value": "spring"
            }
        }
    }
}

regexp query 正則查詢

GET /book/_search
{
    "query": {
        "regexp": {
            "description": {
                "value": "j.*a",
                "flags" : "ALL",
                "max_determinized_states": 10000,
                "rewrite": "constant_score"
            }
        }
    }
}

Fuzzy query

GET /hotel/_search
{
  "query": {
    "fuzzy": {
      "name": {
        "value": "酒店",
        "fuzziness": 0.8
      }
    }
  }
}

RestAPI

 //1.創建請求
SearchRequest request = new SearchRequest("hotel");
//2.構建DSL
request.source().query(QueryBuilders.fuzzyQuery("name","酒店").fuzziness(Fuzziness.AUTO));
 //3.發送請求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
//4.決議回應
extractResponse(response);

9. 復合查詢

布爾查詢是一個或多個查詢子句的組合,每一個子句就是一個子查詢,子查詢的組合方式有:

  • must:必須匹配每個子查詢,類似“與”
  • should:選擇性匹配子查詢,類似“或”
  • must_not:必須不匹配,不參與算分,類似“非”
  • filter:必須匹配,不參與算分

比如在搜索酒店時,除了關鍵字搜索外,我們還可能根據品牌、價格、城市等欄位做過濾:
在這里插入圖片描述
每一個不同的欄位,其查詢的條件、方式都不一樣,必須是多個不同的查詢,而要組合這些查詢,就必須用bool查詢了,

需要注意的是,搜索時,參與打分的欄位越多,查詢的性能也越差,因此這種多條件查詢時,建議這樣做:

  • 搜索框的關鍵字搜索,是全文檢索查詢,使用must查詢,參與算分
  • 其它過濾條件,采用filter查詢,不參與算分

語法所示:

GET /hotel/_search
{
  "query": {
    "bool": {
      "must": [
        {"term": {"city": "上海" }}
      ],
      "should": [
        {"term": {"brand": "皇冠假日" }},
        {"term": {"brand": "華美達" }}
      ],
      "must_not": [
        { "range": { "price": { "lte": 500 } }}
      ],
      "filter": [
        { "range": {"score": { "gte": 45 } }}
      ]
    }
  }
}

RestAPI

 SearchRequest request = new SearchRequest("hotel");
// 2.1.準備BooleanQuery
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
boolQuery.filter(QueryBuilders.rangeQuery("score").gte(45));
boolQuery.must(QueryBuilders.termQuery("city","上海"));
boolQuery.should(QueryBuilders.termQuery("brand","華美達"));
boolQuery.should(QueryBuilders.termQuery("brand","皇冠假日"));
boolQuery.mustNot(QueryBuilders.rangeQuery("price").lte(500));
request.source().query(boolQuery);
 //發送請求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
//決議結果
extractResponse(response);

2)示例

需求:搜索名字包含“如家”,價格不高于400,在坐標31.21,121.5周圍10km范圍內的酒店,

分析:

  • 名稱搜索,屬于全文檢索查詢,應該參與算分,放到must中
  • 價格不高于400,用range查詢,屬于過濾條件,不參與算分,放到must_not中
  • 周圍10km范圍內,用geo_distance查詢,屬于過濾條件,不參與算分,放到filter中
# 復合查詢
GET /hotel/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "term": {
            "brand": {
              "value": "如家"
            }
          }
        }
      ],
      "must_not": [
        {
          "range": {
            "price": {
              "gt": 400
            }
          }
        }
      ],
      "filter": [
        {
          "geo_distance": {
            "distance": "10km",
            "location": {
              "lat": 31.21,
              "lon": 121.5
            }
          }
        }
      ]
    }
  }
}

RestAPI

 SearchRequest request = new SearchRequest("hotel");
        // 2.1.準備BooleanQuery
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
        boolQuery.filter(QueryBuilders
                .geoDistanceQuery("location")
                .distance(10,DistanceUnit.KILOMETERS)
                .point(31.21,121.5));
        
        boolQuery.must(QueryBuilders.termQuery("brand","如家"));
        boolQuery.mustNot(QueryBuilders.rangeQuery("price").gt(500));

        request.source().query(boolQuery);
        //發送請求
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);
        //決議結果
        extractResponse(response);

filter與query對比

filter:僅僅只是按照搜索條件過濾出需要的資料而已,不計算任何相關度分數,對相關度沒有任何影響,

query:會去計算每個document相對于搜索條件的相關度,并按照相關度進行排序,

應用場景:

一般來說,如果你是在進行搜索,需要將最匹配搜索條件的資料先回傳,那么用query 如果你只是要根據一些條件篩選出一部分資料,不關注其排序,那么用filter

filter與query性能

filter,不需要計算相關度分數,不需要按照相關度分數進行排序,同時還有內置的自動cache最常使用filter的資料

query,相反,要計算相關度分數,按照分數進行排序,而且無法cache結果

10. 排序

elasticsearch默認是根據相關度算分(_score)來排序,但是也支持自定義方式對搜索結果排序,可以排序欄位型別有:keyword型別、數值型別、地理坐標型別、日期型別等,

1.普通單欄位排序

keyword、數值、日期型別排序的語法基本一致,

{
 "query": {
   ...條件
 },
 "sort": [{
 "FIELD": {
 "order":"desc"
 }
 }]
}

2.普通多欄位排序

GET /hotel/_search
{
  "query": {
    "match_all": {
      
    }
  },
  "sort": [
    {
      "price": {
        "order": "asc"
      },
      "score": {
        "order": "asc"
      }
    }
  ]
}

RestAPI

		 //1.創建請求
        SearchRequest request = new SearchRequest("hotel");
        //2.構建DSL
        request.source()
                .query(QueryBuilders.matchAllQuery())
                .sort("price",SortOrder.ASC)
                .sort("score",SortOrder.ASC);

        //3.發送請求
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);
        //4.決議回應
        extractResponse(response);

3.地理坐標排序

語法說明:

GET /indexName/_search
{
  "query": {
    "match_all": {}
  },
  "sort": [
    {
      "_geo_distance" : {
          "FIELD" : "緯度,經度", // 檔案中geo_point型別的欄位名、目標坐標點
          "order" : "asc", // 排序方式
          "unit" : "km" // 排序的距離單位
      }
    }
  ]
}

這個查詢的含義是:

  • 指定一個坐標,作為目標點
  • 計算每一個檔案中,指定欄位(必須是geo_point型別)的坐標 到目標點的距離是多少
  • 根據距離排序

示例: 需求描述:實作對酒店資料按照到你的位置坐標的距離升序排序
在這里插入圖片描述
RestAPI

		//1.創建請求
        SearchRequest request = new SearchRequest("hotel");
        //2.構建DSL
        request.source()
                .query(QueryBuilders.matchAllQuery())
                .sort(SortBuilders
                        .geoDistanceSort("location",31.5,121.5)
                        .order(SortOrder.ASC)
                        .unit(DistanceUnit.KILOMETERS));

        //3.發送請求
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);
        //4.決議回應
        extractResponse(response);

11. 分頁

elasticsearch 默認情況下只回傳top10的資料,而如果要查詢更多資料就需要修改分頁引數了,elasticsearch中通過修改from、size引數來控制要回傳的分頁結果:

  • from:從第幾個檔案開始,默認從 0 開始, from = (pageNum - 1) * size
  • size:總共查詢幾個檔案

類似于mysql中的limit ?, ?

基本分頁語法:

GET /hotel/_search
{
  "query": {
    "match_all": {}
  },
  "from": 0, // 分頁開始的位置,默認為0
  "size": 10, // 期望獲取的檔案總數
  "sort": [
    {"price": "asc"}
  ]
}

RestAPI

		 //創建請求
        SearchRequest request = new SearchRequest("hotel");
        //構建DSL
        request.source().query(QueryBuilders
                .matchAllQuery())
                .sort("price",SortOrder.ASC)
                .from(0)
                .size(10);
        //發送請求
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);
        //決議回應
        extractResponse(response);

深度分頁問題

現在,我要查詢990~1000的資料,查詢邏輯要這么寫:

GET /hotel/_search
{
  "query": {
    "match_all": {}
  },
  "from": 990, // 分頁開始的位置,默認為0
  "size": 10, // 期望獲取的檔案總數
  "sort": [
    {"price": "asc"}
  ]
}

這里是查詢990開始的資料,也就是 第990~第1000條 資料,

不過,elasticsearch內部分頁時,必須先查詢 0~1000條,然后截取其中的990 ~ 1000的這10條:
在這里插入圖片描述
查詢TOP1000,如果es是單點模式,這并無太大影響,

但是elasticsearch將來一定是集群,例如我集群有5個節點,我要查詢TOP1000的資料,并不是每個節點查詢200條就可以了,

因為節點A的TOP200,在另一個節點可能排到10000名以外了,

因此要想獲取整個集群的TOP1000,必須先查詢出每個節點的TOP1000,匯總結果后,重新排名,重新截取TOP1000,
在這里插入圖片描述
當查詢分頁深度較大時,匯總資料過多,對記憶體和CPU會產生非常大的壓力,因此elasticsearch會禁止from+ size 超過10000的請求,

針對深度分頁,ES提供了兩種解決方案,官方檔案:

  • search after:分頁時需要排序,原理是從上一次的排序值開始,查詢下一頁資料,官方推薦使用的方式,
  • scroll:原理將排序后的檔案id形成快照,保存在記憶體,官方已經不推薦使用,

分頁查詢的常見實作方案以及優缺點:

  • from + size

    • 優點:支持隨機翻頁
    • 缺點:深度分頁問題,默認查詢上限(from + size)是10000
    • 場景:百度、京東、谷歌、淘寶這樣的隨機翻頁搜索
  • after search

    • 優點:沒有查詢上限(單次查詢的size不超過10000)
    • 缺點:只能向后逐頁查詢,不支持隨機翻頁
    • 場景:沒有隨機翻頁需求的搜索,例如手機向下滾動翻頁
  • scroll

    • 優點:沒有查詢上限(單次查詢的size不超過10000)
    • 缺點:會有額外記憶體消耗,并且搜索結果是非實時的
    • 場景:海量資料的獲取和遷移,從ES7.1開始不推薦,建議用 after search方案,

12. 高亮查詢

高亮原理

什么是高亮顯示呢?

我們在百度,京東搜索時,關鍵字會變成紅色,比較醒目,這叫高亮顯示:
在這里插入圖片描述
高亮顯示的實作分為兩步:

  • 1)給檔案中的所有關鍵字都添加一個標簽,例如<em>標簽
  • 2)頁面給<em>標簽撰寫CSS樣式

實作高亮

高亮的語法

GET /hotel/_search
{
  "query": {
    "match": {
      "FIELD": "TEXT" // 查詢條件,高亮一定要使用全文檢索查詢
    }
  },
  "highlight": {
    "fields": { // 指定要高亮的欄位
      "FIELD": {
        "pre_tags": "<em>",  // 用來標記高亮欄位的前置標簽
        "post_tags": "</em>" // 用來標記高亮欄位的后置標簽
      }
    }
  }
}

注意:

  • 高亮是對關鍵字高亮,因此搜索條件必須帶有關鍵字,而不能是范圍這樣的查詢,
  • 默認情況下,高亮的欄位,必須與搜索指定的欄位一致,否則無法高亮
  • 如果要對非搜索欄位高亮,則需要添加一個屬性:required_field_match=false

在這里插入圖片描述
ResAPI

		// 1.準備Request
        SearchRequest request = new SearchRequest("hotel");
        // 2.準備DSL
        // 2.1.query
        request.source().query(QueryBuilders.matchQuery("all", "如家"));
        // 2.2.高亮
        request.source().highlighter(new HighlightBuilder().field("name").requireFieldMatch(false));
        // 3.發送請求
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);
        // 4.決議回應
        SearchHits searchHits = response.getHits();
        // 4.1.獲取總條數
        long total = searchHits.getTotalHits().value;
        System.out.println("共搜索到" + total + "條資料");
        // 4.2.檔案陣列
        SearchHit[] hits = searchHits.getHits();
        // 4.3.遍歷
        for (SearchHit hit : hits) {
            // 獲取檔案source
            String json = hit.getSourceAsString();
            // 反序列化
            HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);
            // 獲取高亮結果
            Map<String, HighlightField> highlightFields = hit.getHighlightFields();
            if (!CollectionUtils.isEmpty(highlightFields)) {
                // 根據欄位名獲取高亮結果
                HighlightField highlightField = highlightFields.get("name");
                if (highlightField != null) {
                    // 獲取高亮值
                    String name = highlightField.getFragments()[0].string();
                    // 覆寫非高亮結果
                    hotelDoc.setName(name);
                }
            }
            System.out.println("hotelDoc = " + hotelDoc);
        }

13. 聚合查詢

聚合(aggregations)可以讓我們極其方便的實作對資料的統計、分析、運算,例如:

  • 什么品牌的手機最受歡迎?
  • 這些手機的平均價格、最高價格、最低價格?
  • 這些手機每月的銷售情況如何?

實作這些統計功能的比資料庫的sql要方便的多,而且查詢速度非常快,可以實作近實時搜索效果,

聚合的種類

聚合常見的有三類:

  • 桶(Bucket)聚合:用來對檔案做分組

  • TermAggregation:按照檔案欄位值分組,例如按照品牌值分組、按照國家分組

  • Date Histogram:按照日期階梯分組,例如一周為一組,或者一月為一組

  • 度量(Metric)聚合:用以計算一些值,比如:最大值、最小值、平均值等

    • Avg:求平均值
    • Max:求最大值
    • Min:求最小值
    • Stats:同時求max、min、avg、sum等
  • 管道(pipeline)聚合:其它聚合的結果為基礎做聚合

Bucket聚合語法如下:

GET /hotel/_search
{
  "size": 0,  // 設定size為0,結果中不包含檔案,只包含聚合結果
  "aggs": { // 定義聚合
    "brandAgg": { //給聚合起個名字
      "terms": { // 聚合的型別,按照品牌值聚合,所以選擇term
        "field": "brand", // 參與聚合的欄位
        "size": 20 // 希望獲取的聚合結果數量
      }
    }
  }
}

結果:
在這里插入圖片描述
RestAPI

在這里插入圖片描述
在這里插入圖片描述

		 //創建請求
        SearchRequest request = new SearchRequest("hotel");
        //創建DSL
        request.source().aggregation(AggregationBuilders
                .terms("brand_agg")
                .field("brand")
                .size(20)).size(0);
        //發送請求
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);
        //決議回應
        Aggregations aggregations = response.getAggregations();
        //根據名稱獲取結果
        Terms brand_agg = aggregations.get("brand_agg");
        //拿到桶
        List<? extends Terms.Bucket> buckets = brand_agg.getBuckets();
        //遍歷桶
        for (Terms.Bucket bucket : buckets) {
            System.out.println(bucket.getKeyAsString());
        }

聚合結果排序

默認情況下,Bucket聚合會統計Bucket內的檔案數量,記為_count,并且按照_count降序排序,

我們可以指定order屬性,自定義聚合的排序方式:

GET /hotel/_search
{
  "size": 0, 
  "aggs": {
    "brandAgg": {
      "terms": {
        "field": "brand",
        "order": {
          "_count": "asc" // 按照_count升序排列
        },
        "size": 20
      }
    }
  }
}
 SearchRequest request = new SearchRequest("hotel");
        //創建DSL
        request.source().aggregation(AggregationBuilders
                .terms("brand_agg")
                .field("brand")
                .size(20)).size(0)
                .sort("_count",SortOrder.ASC);
        //發送請求
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);

限定聚合范圍

默認情況下,Bucket聚合是對索引庫的所有檔案做聚合,但真實場景下,用戶會輸入搜索條件,因此聚合必須是對搜索結果聚合,那么聚合必須添加限定條件,

我們可以限定要聚合的檔案范圍,只要添加query條件即可:

GET /hotel/_search
{
  "query": {
    "range": {
      "price": {
        "lte": 200 // 只對200元以下的檔案聚合
      }
    }
  }, 
  "size": 0, 
  "aggs": {
    "brandAgg": {
      "terms": {
        "field": "brand",
        "size": 20
      }
    }
  }
}

RestAPI

		//創建請求
        SearchRequest request = new SearchRequest("hotel");
        //創建DSL
        request.source().query(QueryBuilders.rangeQuery("price").lte(200));
        request.source()
                .aggregation(AggregationBuilders
                .terms("brand_agg")
                .field("brand")
                .size(20)).size(0);
        //發送請求
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);
        //決議回應
        Aggregations aggregations = response.getAggregations();
        //根據名稱獲取結果
        Terms brand_agg = aggregations.get("brand_agg");
        //拿到桶
        List<? extends Terms.Bucket> buckets = brand_agg.getBuckets();
        //遍歷桶
        for (Terms.Bucket bucket : buckets) {
            System.out.println(bucket.getKeyAsString());
        }


Metric聚合語法

我們對酒店按照品牌分組,形成了一個個桶,現在我們需要對桶內的酒店做運算,獲取每個品牌的用戶評分的min、max、avg等值,

這就要用到Metric聚合了,例如stat聚合:就可以獲取min、max、avg等結果,

語法如下:

GET /hotel/_search
{
  "size": 0, 
  "aggs": {
    "brandAgg": { 
      "terms": { 
        "field": "brand", 
        "size": 20
      },
      "aggs": { // 是brands聚合的子聚合,也就是分組后對每組分別計算
        "score_stats": { // 聚合名稱
          "stats": { // 聚合型別,這里stats可以計算min、max、avg等
            "field": "score" // 聚合欄位,這里是score
          }
        }
      }
    }
  }
}

這次的score_stats聚合是在brandAgg的聚合內部嵌套的子聚合,因為我們需要在每個桶分別計算,

另外,我們還可以給聚合結果做個排序,例如按照每個桶的酒店平均分做排序:
在這里插入圖片描述
RestAPI

		//創建請求
        SearchRequest request = new SearchRequest("hotel");
        //創建DSL
        request.source().aggregation(AggregationBuilders
                .terms("brand_agg")
                .field("brand")
                .subAggregation(AggregationBuilders.stats("score_stats").field("score"))
                .size(20))
                .size(0);

        //發送請求
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);
        //決議回應
        Aggregations aggregations = response.getAggregations();
        //根據名稱獲取結果
        Terms brand_agg = aggregations.get("brand_agg");
        //拿到桶
        List<? extends Terms.Bucket> buckets = brand_agg.getBuckets();
        //遍歷桶
        for (Terms.Bucket bucket : buckets) {
            System.out.println(bucket.getKeyAsString());
        }

六) 聚合案例

電視案例

創建索引及映射

PUT /tvs
PUT /tvs/_search
{			
			"properties": {
				"price": {
					"type": "long"
				},
				"color": {
					"type": "keyword"
				},
				"brand": {
					"type": "keyword"
				},
				"sold_date": {
					"type": "date"
				}
			}
}

插入資料

POST /tvs/_bulk
{ "index": {}}
{ "price" : 1000, "color" : "紅色", "brand" : "長虹", "sold_date" : "2019-10-28" }
{ "index": {}}
{ "price" : 2000, "color" : "紅色", "brand" : "長虹", "sold_date" : "2019-11-05" }
{ "index": {}}
{ "price" : 3000, "color" : "綠色", "brand" : "小米", "sold_date" : "2019-05-18" }
{ "index": {}}
{ "price" : 1500, "color" : "藍色", "brand" : "TCL", "sold_date" : "2019-07-02" }
{ "index": {}}
{ "price" : 1200, "color" : "綠色", "brand" : "TCL", "sold_date" : "2019-08-19" }
{ "index": {}}
{ "price" : 2000, "color" : "紅色", "brand" : "長虹", "sold_date" : "2019-11-05" }
{ "index": {}}
{ "price" : 8000, "color" : "紅色", "brand" : "三星", "sold_date" : "2020-01-01" }
{ "index": {}}
{ "price" : 2500, "color" : "藍色", "brand" : "小米", "sold_date" : "2020-02-12" }

需求1 統計哪種顏色的電視銷量最高

GET /tvs/_search
{
    "size" : 0,
    "aggs" : { 
        "popular_colors" : { 
            "terms" : { 
              "field" : "color"
            }
        }
    }
}

查詢條件決議

size:只獲取聚合結果,而不要執行聚合的原始資料
aggs:固定語法,要對一份資料執行分組聚合操作
popular_colors:就是對每個aggs,都要起一個名字,
terms:根據欄位的值進行分組
field:根據指定的欄位的值進行分組

回傳

{
  "took" : 18,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 8,
      "relation" : "eq"
    },
    "max_score" : null,
    "hits" : [ ]
  },
  "aggregations" : {
    "popular_colors" : {
      "doc_count_error_upper_bound" : 0,
      "sum_other_doc_count" : 0,
      "buckets" : [
        {
          "key" : "紅色",
          "doc_count" : 4
        },
        {
          "key" : "綠色",
          "doc_count" : 2
        },
        {
          "key" : "藍色",
          "doc_count" : 2
        }
      ]
    }
  }
}

回傳結果決議

hits.hits:我們指定了size是0,所以hits.hits就是空的
aggregations:聚合結果
popular_color:我們指定的某個聚合的名稱
buckets:根據我們指定的field劃分出的buckets
key:每個bucket對應的那個值
doc_count:這個bucket分組內,有多少個資料
數量,其實就是這種顏色的銷量

每種顏色對應的bucket中的資料的默認的排序規則:按照doc_count降序排序

需求2 統計每種顏色電視平均價格

GET /tvs/_search
{
   "size" : 0,
   "aggs": {
      "colors": {
         "terms": {
            "field": "color"
         },
         "aggs": { 
            "avg_price": { 
               "avg": {
                  "field": "price" 
               }
            }
         }
      }
   }
}

在一個aggs執行的bucket操作(terms),平級的json結構下,再加一個aggs,這個第二個aggs內部,同樣取個名字,執行一個metric操作,avg,對之前的每個bucket中的資料的指定的field,price field,求一個平均值

回傳:

{
  "took" : 4,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 8,
      "relation" : "eq"
    },
    "max_score" : null,
    "hits" : [ ]
  },
  "aggregations" : {
    "colors" : {
      "doc_count_error_upper_bound" : 0,
      "sum_other_doc_count" : 0,
      "buckets" : [
        {
          "key" : "紅色",
          "doc_count" : 4,
          "avg_price" : {
            "value" : 3250.0
          }
        },
        {
          "key" : "綠色",
          "doc_count" : 2,
          "avg_price" : {
            "value" : 2100.0
          }
        },
        {
          "key" : "藍色",
          "doc_count" : 2,
          "avg_price" : {
            "value" : 2000.0
          }
        }
      ]
    }
  }
}

buckets,除了key和doc_count
avg_price:我們自己取的metric aggs的名字
value:我們的metric計算的結果,每個bucket中的資料的price欄位求平均值后的結果

相當于sql: select avg(price) from tvs group by color

需求3 繼續下鉆分析

每個顏色下,平均價格及每個顏色下,每個品牌的平均價格

GET /tvs/_search 
{
  "size": 0,
  "aggs": {
    "group_by_color": {
      "terms": {
        "field": "color"
      },
      "aggs": {
        "color_avg_price": {
          "avg": {
            "field": "price"
          }
        },
        "group_by_brand": {
          "terms": {
            "field": "brand"
          },
          "aggs": {
            "brand_avg_price": {
              "avg": {
                "field": "price"
              }
            }
          }
        }
      }
    }
  }
}

需求4:更多的metric

count:bucket,terms,自動就會有一個doc_count,就相當于是count
avg:avg aggs,求平均值
max:求一個bucket內,指定field值最大的那個資料
min:求一個bucket內,指定field值最小的那個資料
sum:求一個bucket內,指定field值的總和

GET /tvs/_search
{
   "size" : 0,
   "aggs": {
      "colors": {
         "terms": {
            "field": "color"
         },
         "aggs": {
            "avg_price": { "avg": { "field": "price" } },
            "min_price" : { "min": { "field": "price"} }, 
            "max_price" : { "max": { "field": "price"} },
            "sum_price" : { "sum": { "field": "price" } } 
         }
      }
   }
}

需求5:劃分范圍 histogram

GET /tvs/_search
{
   "size" : 0,
   "aggs":{
      "price":{
         "histogram":{ 
            "field": "price",
            "interval": 2000
         },
         "aggs":{
            "income": {
               "sum": { 
                 "field" : "price"
               }
             }
         }
      }
   }
}

histogram:類似于terms,也是進行bucket分組操作,接收一個field,按照這個field的值的各個范圍區間,進行bucket分組操作

"histogram":{ 
  "field": "price",
  "interval": 2000
}

interval:2000,劃分范圍,02000,20004000,40006000,60008000,8000~10000,buckets

bucket有了之后,一樣的,去對每個bucket執行avg,count,sum,max,min,等各種metric操作,聚合分析

需求6:按照日期分組聚合

date_histogram,按照我們指定的某個date型別的日期field,以及日期interval,按照一定的日期間隔,去劃分bucket

min_doc_count:即使某個日期interval,2017-01-01~2017-01-31中,一條資料都沒有,那么這個區間也是要回傳的,不然默認是會過濾掉這個區間的
extended_bounds,min,max:劃分bucket的時候,會限定在這個起始日期,和截止日期內

GET /tvs/_search
{
   "size" : 0,
   "aggs": {
      "sales": {
         "date_histogram": {
            "field": "sold_date",
            "interval": "month", 
            "format": "yyyy-MM-dd",
            "min_doc_count" : 0, 
            "extended_bounds" : { 
                "min" : "2019-01-01",
                "max" : "2020-12-31"
            }
         }
      }
   }
}

需求7 統計每季度每個品牌的銷售額

GET /tvs/_search 
{
  "size": 0,
  "aggs": {
    "group_by_sold_date": {
      "date_histogram": {
        "field": "sold_date",
        "interval": "quarter",
        "format": "yyyy-MM-dd",
        "min_doc_count": 0,
        "extended_bounds": {
          "min": "2019-01-01",
          "max": "2020-12-31"
        }
      },
      "aggs": {
        "group_by_brand": {
          "terms": {
            "field": "brand"
          },
          "aggs": {
            "sum_price": {
              "sum": {
                "field": "price"
              }
            }
          }
        },
        "total_sum_price": {
          "sum": {
            "field": "price"
          }
        }
      }
    }
  }
}

需求8 :搜索與聚合結合,查詢某個品牌按顏色銷量

搜索與聚合可以結合起來,

sql select count(*)

from tvs

where brand like “%小米%”

group by color

es aggregation,scope,任何的聚合,都必須在搜索出來的結果資料中之行,搜索結果,就是聚合分析操作的scope

GET /tvs/_search 
{
  "size": 0,
  "query": {
    "term": {
      "brand": {
        "value": "小米"
      }
    }
  },
  "aggs": {
    "group_by_color": {
      "terms": {
        "field": "color"
      }
    }
  }
}

需求9 global bucket:單個品牌與所有品牌銷量對比

aggregation,scope,一個聚合操作,必須在query的搜索結果范圍內執行

出來兩個結果,一個結果,是基于query搜索結果來聚合的; 一個結果,是對所有資料執行聚合的

GET /tvs/_search 
{
  "size": 0, 
  "query": {
    "term": {
      "brand": {
        "value": "小米"
      }
    }
  },
  "aggs": {
    "single_brand_avg_price": {
      "avg": {
        "field": "price"
      }
    },
    "all": {
      "global": {},
      "aggs": {
        "all_brand_avg_price": {
          "avg": {
            "field": "price"
          }
        }
      }
    }
  }
}

需求10:過濾+聚合:統計價格大于1200的電視平均價格

搜索+聚合

過濾+聚合

GET /tvs/_search 
{
  "size": 0,
  "query": {
    "constant_score": {
      "filter": {
        "range": {
          "price": {
            "gte": 1200
          }
        }
      }
    }
  },
  "aggs": {
    "avg_price": {
      "avg": {
        "field": "price"
      }
    }
  }
}

需求11 bucket filter:統計品牌最近一個月的平均價格

GET /tvs/_search 
{
  "size": 0,
  "query": {
    "term": {
      "brand": {
        "value": "小米"
      }
    }
  },
  "aggs": {
    "recent_150d": {
      "filter": {
        "range": {
          "sold_date": {
            "gte": "now-150d"
          }
        }
      },
      "aggs": {
        "recent_150d_avg_price": {
          "avg": {
            "field": "price"
          }
        }
      }
    },
    "recent_140d": {
      "filter": {
        "range": {
          "sold_date": {
            "gte": "now-140d"
          }
        }
      },
      "aggs": {
        "recent_140d_avg_price": {
          "avg": {
            "field": "price"
          }
        }
      }
    },
    "recent_130d": {
      "filter": {
        "range": {
          "sold_date": {
            "gte": "now-130d"
          }
        }
      },
      "aggs": {
        "recent_130d_avg_price": {
          "avg": {
            "field": "price"
          }
        }
      }
    }
  }
}

aggs.filter,針對的是聚合去做的

如果放query里面的filter,是全域的,會對所有的資料都有影響

但是,如果,比如說,你要統計,長虹電視,最近1個月的平均值; 最近3個月的平均值; 最近6個月的平均值

bucket filter:對不同的bucket下的aggs,進行filter

需求12 排序:按每種顏色的平均銷售額降序排序

GET /tvs/_search 
{
  "size": 0,
  "aggs": {
    "group_by_color": {
      "terms": {
        "field": "color",
        "order": {
          "avg_price": "asc"
        }
      },
      "aggs": {
        "avg_price": {
          "avg": {
            "field": "price"
          }
        }
      }
    }
  }
}

相當于sql子表資料欄位可以立刻使用,

需求13 排序:按每種顏色的每種品牌平均銷售額降序排序

GET /tvs/_search  
{
  "size": 0,
  "aggs": {
    "group_by_color": {
      "terms": {
        "field": "color"
      },
      "aggs": {
        "group_by_brand": {
          "terms": {
            "field": "brand",
            "order": {
              "avg_price": "desc"
            }
          },
          "aggs": {
            "avg_price": {
              "avg": {
                "field": "price"
              }
            }
          }
        }
      }
    }
  }
}

轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/423390.html

標籤:其他

上一篇:人工智能與智能系統2-> 機器人學2 | 時間與運動

下一篇:018 The Scala Programming Language

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 網閘典型架構簡述

    網閘架構一般分為兩種:三主機的三系統架構網閘和雙主機的2+1架構網閘。 三主機架構分別為內端機、外端機和仲裁機。三機無論從軟體和硬體上均各自獨立。首先從硬體上來看,三機都用各自獨立的主板、記憶體及存盤設備。從軟體上來看,三機有各自獨立的作業系統。這樣能達到完全的三機獨立。對于“2+1”系統,“2”分為 ......

    uj5u.com 2020-09-10 02:00:44 more
  • 如何從xshell上傳檔案到centos linux虛擬機里

    如何從xshell上傳檔案到centos linux虛擬機里及:虛擬機CentOs下執行 yum -y install lrzsz命令,出現錯誤:鏡像無法找到軟體包 前言 一、安裝lrzsz步驟 二、上傳檔案 三、遇到的問題及解決方案 總結 前言 提示:其實很簡單,往虛擬機上安裝一個上傳檔案的工具 ......

    uj5u.com 2020-09-10 02:00:47 more
  • 一、SQLMAP入門

    一、SQLMAP入門 1、判斷是否存在注入 sqlmap.py -u 網址/id=1 id=1不可缺少。當注入點后面的引數大于兩個時。需要加雙引號, sqlmap.py -u "網址/id=1&uid=1" 2、判斷文本中的請求是否存在注入 從文本中加載http請求,SQLMAP可以從一個文本檔案中 ......

    uj5u.com 2020-09-10 02:00:50 more
  • Metasploit 簡單使用教程

    metasploit 簡單使用教程 浩先生, 2020-08-28 16:18:25 分類專欄: kail 網路安全 linux 文章標簽: linux資訊安全 編輯 著作權 metasploit 使用教程 前言 一、Metasploit是什么? 二、準備作業 三、具體步驟 前言 Msfconsole ......

    uj5u.com 2020-09-10 02:00:53 more
  • 游戲逆向之驅動層與用戶層通訊

    驅動層代碼: #pragma once #include <ntifs.h> #define add_code CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS) /* 更多游戲逆向視頻www.yxfzedu.com ......

    uj5u.com 2020-09-10 02:00:56 more
  • 北斗電力時鐘(北斗授時服務器)讓網路資料更精準

    北斗電力時鐘(北斗授時服務器)讓網路資料更精準 北斗電力時鐘(北斗授時服務器)讓網路資料更精準 京準電子科技官微——ahjzsz 近幾年,資訊技術的得了快速發展,互聯網在逐漸普及,其在人們生活和生產中都得到了廣泛應用,并且取得了不錯的應用效果。計算機網路資訊在電力系統中的應用,一方面使電力系統的運行 ......

    uj5u.com 2020-09-10 02:01:03 more
  • 【CTF】CTFHub 技能樹 彩蛋 writeup

    ?碎碎念 CTFHub:https://www.ctfhub.com/ 筆者入門CTF時時剛開始刷的是bugku的舊平臺,后來才有了CTFHub。 感覺不論是網頁UI設計,還是題目質量,賽事跟蹤,工具軟體都做得很不錯。 而且因為獨到的金幣制度的確讓人有一種想去刷題賺金幣的感覺。 個人還是非常喜歡這個 ......

    uj5u.com 2020-09-10 02:04:05 more
  • 02windows基礎操作

    我學到了一下幾點 Windows系統目錄結構與滲透的作用 常見Windows的服務詳解 Windows埠詳解 常用的Windows注冊表詳解 hacker DOS命令詳解(net user / type /md /rd/ dir /cd /net use copy、批處理 等) 利用dos命令制作 ......

    uj5u.com 2020-09-10 02:04:18 more
  • 03.Linux基礎操作

    我學到了以下幾點 01Linux系統介紹02系統安裝,密碼啊破解03Linux常用命令04LAMP 01LINUX windows: win03 8 12 16 19 配置不繁瑣 Linux:redhat,centos(紅帽社區版),Ubuntu server,suse unix:金融機構,證券,銀 ......

    uj5u.com 2020-09-10 02:04:30 more
  • 05HTML

    01HTML介紹 02頭部標簽講解03基礎標簽講解04表單標簽講解 HTML前段語言 js1.了解代碼2.根據代碼 懂得挖掘漏洞 (POST注入/XSS漏洞上傳)3.黑帽seo 白帽seo 客戶網站被黑帽植入劫持代碼如何處理4.熟悉html表單 <html><head><title>TDK標題,描述 ......

    uj5u.com 2020-09-10 02:04:36 more
最新发布
  • 2023年最新微信小程式抓包教程

    01 開門見山 隔一個月發一篇文章,不過分。 首先回顧一下《微信系結手機號資料庫被脫庫事件》,我也是第一時間得知了這個訊息,然后跟蹤了整件事情的經過。下面是這起事件的相關截圖以及近日流出的一萬條資料樣本: 個人認為這件事也沒什么,還不如關注一下之前45億快遞資料查詢渠道疑似在近日復活的訊息。 訊息是 ......

    uj5u.com 2023-04-20 08:48:24 more
  • web3 產品介紹:metamask 錢包 使用最多的瀏覽器插件錢包

    Metamask錢包是一種基于區塊鏈技術的數字貨幣錢包,它允許用戶在安全、便捷的環境下管理自己的加密資產。Metamask錢包是以太坊生態系統中最流行的錢包之一,它具有易于使用、安全性高和功能強大等優點。 本文將詳細介紹Metamask錢包的功能和使用方法。 一、 Metamask錢包的功能 數字資 ......

    uj5u.com 2023-04-20 08:47:46 more
  • vulnhub_Earth

    前言 靶機地址->>>vulnhub_Earth 攻擊機ip:192.168.20.121 靶機ip:192.168.20.122 參考文章 https://www.cnblogs.com/Jing-X/archive/2022/04/03/16097695.html https://www.cnb ......

    uj5u.com 2023-04-20 07:46:20 more
  • 從4k到42k,軟體測驗工程師的漲薪史,給我看哭了

    清明節一過,盲猜大家已經無心上班,在數著日子準備過五一,但一想到銀行卡里的余額……瞬間心情就不美麗了。最近,2023年高校畢業生就業調查顯示,本科畢業月平均起薪為5825元。調查一出,便有很多同學表示自己又被平均了。看著這一資料,不免讓人想到前不久中國青年報的一項調查:近六成大學生認為畢業10年內會 ......

    uj5u.com 2023-04-20 07:44:00 more
  • 最新版本 Stable Diffusion 開源 AI 繪畫工具之中文自動提詞篇

    🎈 標簽生成器 由于輸入正向提示詞 prompt 和反向提示詞 negative prompt 都是使用英文,所以對學習母語的我們非常不友好 使用網址:https://tinygeeker.github.io/p/ai-prompt-generator 這個網址是為了讓大家在使用 AI 繪畫的時候 ......

    uj5u.com 2023-04-20 07:43:36 more
  • 漫談前端自動化測驗演進之路及測驗工具分析

    隨著前端技術的不斷發展和應用程式的日益復雜,前端自動化測驗也在不斷演進。隨著 Web 應用程式變得越來越復雜,自動化測驗的需求也越來越高。如今,自動化測驗已經成為 Web 應用程式開發程序中不可或缺的一部分,它們可以幫助開發人員更快地發現和修復錯誤,提高應用程式的性能和可靠性。 ......

    uj5u.com 2023-04-20 07:43:16 more
  • CANN開發實踐:4個DVPP記憶體問題的典型案例解讀

    摘要:由于DVPP媒體資料處理功能對存放輸入、輸出資料的記憶體有更高的要求(例如,記憶體首地址128位元組對齊),因此需呼叫專用的記憶體申請介面,那么本期就分享幾個關于DVPP記憶體問題的典型案例,并給出原因分析及解決方法。 本文分享自華為云社區《FAQ_DVPP記憶體問題案例》,作者:昇騰CANN。 DVPP ......

    uj5u.com 2023-04-20 07:43:03 more
  • msf學習

    msf學習 以kali自帶的msf為例 一、msf核心模塊與功能 msf模塊都放在/usr/share/metasploit-framework/modules目錄下 1、auxiliary 輔助模塊,輔助滲透(埠掃描、登錄密碼爆破、漏洞驗證等) 2、encoders 編碼器模塊,主要包含各種編碼 ......

    uj5u.com 2023-04-20 07:42:59 more
  • Halcon軟體安裝與界面簡介

    1. 下載Halcon17版本到到本地 2. 雙擊安裝包后 3. 步驟如下 1.2 Halcon軟體安裝 界面分為四大塊 1. Halcon的五個助手 1) 影像采集助手:與相機連接,設定相機引數,采集影像 2) 標定助手:九點標定或是其它的標定,生成標定檔案及內參外參,可以將像素單位轉換為長度單位 ......

    uj5u.com 2023-04-20 07:42:17 more
  • 在MacOS下使用Unity3D開發游戲

    第一次發博客,先發一下我的游戲開發環境吧。 去年2月份買了一臺MacBookPro2021 M1pro(以下簡稱mbp),這一年來一直在用mbp開發游戲。我大致分享一下我的開發工具以及使用體驗。 1、Unity 官網鏈接: https://unity.cn/releases 我一般使用的Apple ......

    uj5u.com 2023-04-20 07:40:19 more