索引,映射,檔案,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/SolrCloud | Elasticsearch |
|---|---|---|
| 社區和開發者 | Apache軟體基金和社區支持 | 單一商業物體及其員工 |
| 節點發現 | Apache Zookeeper.在大量專案中成熟且經過實戰測驗 | Zen內置于Elasticsearch本身,需要專用的主節點才能進行裂腦保護 |
| 碎片放置 | 本質上是靜態,需要手動作業來遷移分片,從Solr 7開始- AutoscalingAPI允許一些動態操作 | 動態,可以根據群集狀態按需移動分片 |
| 高速快取 | 全域,每個段更改無效 | 每段,更適合動態更改資料 |
| 分析引擎性能 | 非常適合精確計算的靜態資料 | 結果的準確性取決于資料放置 |
| 全文搜索功能 | 基于Lucene的語言分析,多建議,拼寫檢查,豐富的高亮顯示支持 | 基于Lucene的語言分析,單一建議API實作, 高亮顯示重新計算 |
| DevOps支持 | 尚未完全,但即將到來 | 非常好的API |
| 非平面資料處理 | 嵌套檔案和父子支持 | 嵌套和物件型別的自然支持允許幾乎無限的嵌套和父-子支持 |
| 查詢DSL | JSON (有限),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)]](https://img.uj5u.com/2022/02/06/299079060722098.png)
而Json檔案中往往包含很多的欄位(Field),類似于資料庫中的列,
索引和映射
索引(Index),就是相同型別的檔案的集合,
例如:
- 所有用戶檔案,就可以組織在一起,稱為用戶的索引;
- 所有商品的檔案,可以組織在一起,稱為商品的索引;
- 所有訂單的檔案,可以組織在一起,稱為訂單的索引;

因此,我們可以把索引當做是資料庫中的表,
資料庫的表會有約束資訊,用來定義表的結構、欄位的名稱、型別等資訊,因此,索引庫中就有映射(mapping),是索引中檔案的欄位約束資訊,類似表的結構約束,
mysql與elasticsearch比較
我們統一的把mysql與elasticsearch的概念做一下對比:
| MySQL | Elasticsearch | 說明 |
|---|---|---|
| Table | Index | 索引(index),就是檔案的集合,類似資料庫的表(table) |
| Row | Document | 檔案(Document),就是一條條的資料,類似資料庫中的行(Row),檔案都是JSON格式 |
| Column | Field | 欄位(Field),就是JSON檔案中的欄位,類似資料庫中的列(Column) |
| Schema | Mapping | Mapping(映射)是索引中檔案的約束,例如欄位型別約束,類似資料庫的表結構(Schema) |
| SQL | DSL | DSL是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
標籤:其他
