---
tags: Elasticsearch學習
---
# ElasticSearchr學習
## ElasticSearchr 介紹
### 引言
當我們在購物網站搜尋=>"手機殼",它如果在SQL中執行可能會使用這樣的語法
```sql=
select *form item where name like %手機殼%
```
但是如果用SQL搜尋使用%索引會失效,效率是極低的

這時候如果再把關鍵字改成搜尋=>"殼手機",並且還是搜尋到了,搜尋的方式疑似會將我們輸入的關鍵字
拆分開來,再去匹配商品內容,以我們學習到的sql是無法做到這件事情的

ElasticSearchr能做到:
* 在大數據中執行搜索功能時,如果使用MySql,效率太低
* 如果關鍵字輸入不準確,一樣可以搜索到想要的資料
* 將搜索關鍵字,以紅色字體展示
### ES的介紹
ES是一個使用Java語言並且基於[Lucene](https://lucene.apache.org/)編寫的搜索引擎框架,他提供了分布式的全文搜索功能,提供了一個統一的基於RESTful風格的WebApi接口,官方客戶也對多種語言都提供了相應的API
Lucene:本身就是一個搜索引擎的底層。
Lucene官方網站也不推薦直接使用Lucene,再它官方網站中有提供一個框架叫做Solr。
Solr也是基於Lucene去開發的一個框架,因此很多人會拿Solr跟ES做一個對比。
分布式:ES主要是為了突出它的橫向擴展能力(搭建集群)。
全文檢索:將一段詞語進行分詞,並且將分出的單個詞語統一的放到一個分詞庫中,在搜索時,根據關鍵字去分詞庫中檢索,找到匹配的內容。(倒排索引)

RESTful風格的Web風格接口:操作ES很簡單,只需要發送一個HTTP請求,並且根據請求的方式的不同,攜帶參數的不同,執行相應的功能。
應用廣泛:Github.com,WIKI,GoldMan用ES每天維護將近10TB的數據。
ES官方學習網站:
[官方Document](https://www.elastic.co/guide/index.html)
[Elasticsearch: 权威指南](https://www.elastic.co/guide/cn/elasticsearch/guide/2.x/index.html)
### ES和Solr區別
1. Solr在查詢死數據時,速度相對ES更快一些。但是數據如果是常常改變的,Solr的查詢速度會降低很多,ES的查詢效率基本沒有變化。
2. Solr搭建基於需要依賴Zookeeper來幫助管理。ES本身就是支持集群的搭建,不需要第三方的介入。
3. 最開始Solr的社區可以說是非常火爆,針對中文的資料並不是很多。在ES出現之後,ES的討論社區火爆程度直線上升,ES的文件非常健全。
4. ES對現在云計算和大數據支持的特別好。
### 倒排索引
1. 將存放的數據,已依定的方式進行分詞,並且將分詞的內容存放倒一個單獨的分詞庫中。
2. 當用戶去查詢數據時,會將用戶的查詢關鍵字進行分詞。
3. 然後去分詞庫中匹配內容,最終得到數據id標示。
4. 根據id標示去存放數據的位置拉取到指定的數據。

## ElasticSearchr的安裝
### 安裝ES&Kibana
```yaml=
version:"3.1"
services:
elasticsearch:
image: elasticsearch:6.5.4
restart: always
container_name: elasticsearch
potrts:
-9200:9200
kibana:
image: kibana:6.5.4
restart: always
container_name: kibana
potrts:
-5601:5601
enviroment:
-elasticsearch_url=http://192.168.199.109:9200
depens_on:
-elasticsearch
```
Kevin版本:
```yaml=
version: "3.2"
services:
elasticsearch:
image: elasticsearch:6.5.4
container_name: elasticsearch-654
ports:
- "9200:9200"
- "9300:9300"
environment:
- cluster.name=docker-cluster
- bootstrap.memory_lock=true
- http.host=0.0.0.0
- http.port=9200
- transport.host=127.0.0.1
- "ES_JAVA_OPTS=-Xms512m -Xmx512m"
- "http.cors.allow-origin=http://127.0.0.1:1358"
- "http.cors.enabled=true"
- "http.cors.allow-headers=X-Requested-With,X-Auth-Token,Content-Type,Content-Length,Authorization"
- "http.cors.allow-credentials=true"
ulimits:
memlock:
soft: -1
hard: -1
volumes:
- es_data:/usr/share/elasticsearch/data
networks:
- esnet
kibana:
image: kibana:6.5.4
container_name: kibana-654
environment:
SERVER_NAME: kibana-server
ELASTICSEARCH_URL: http://elasticsearch:9200
networks:
- esnet
depends_on:
- elasticsearch
ports:
- "5601:5601"
cerebro:
image: yannart/cerebro:latest
container_name: cerebro-073
networks:
- esnet
ports:
- "9900:9000"
depends_on:
- elasticsearch
dejavu:
image: appbaseio/dejavu:latest
container_name: dejavu
networks:
- esnet
ports:
- "1358:1358"
volumes:
es_data:
driver: local
networks:
esnet:
driver: bridge
```
在透過`docker-compose up -d` 啟動

先確認ES有啟動成功,如果有看到下列的JSON格式化面就表示是沒有問題的

若ES沒問題再來訪問kibana
`http://127.0.0.1:5601/app/kibana`
進入kibana後,可以進入dev tools執行 webapi操作

### 安裝分詞器IK分詞器
ES本身的分詞器對中文的處理不太友善,所以可以去下載[IK分詞器],不過IK分詞器對繁體中文也不太友善,裡面的字典檔都是簡體字
(https://github.com/medcl/elasticsearch-analysis-ik),並且版本要選擇跟ES一樣的版本
進去ES的容器內部,進入內部bin目錄文件下,執行腳本文件
`./elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v6.5.4/elasticsearch-analysis-ik-6.5.4.zip`
`https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v6.5.4/elasticsearch-analysis-ik-6.5.4.zip`
#### 離線安裝
1. 先到GitHub下載,對應版本的IK分詞器
2. 解壓縮到目錄中`E:\es`
3. 到解壓縮的目錄中 執行`docker cp . es的容器編號:/usr/share/elasticsearch/plugins/ik/.`
4. 重啟ES `docker restart 容器編號`

#### 在線安裝
1. 執行`docker ps` 查詢容器的編號
2. 執行`docker exec -it 容器編號 bash`
3. 執行`ls`
4. 執行`cd bin/`
5. 執行`ls`
6. 執行`./elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v6.5.4/elasticsearch-analysis-ik-6.5.4.zip
-> Downloading https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v6.5.4/elasticsearch-analysis-ik-6.5.4.zip`
7. 重啟ES `docker restart 容器編號`


默認的分詞器

可以測試一下IK分詞器

## ElasticSearch基本操作
### ES的結構
#### 索引index
:::info
ES的服務中,可以建立多個索引。
每一個索引默認被分成5片儲存。
每一個分片都會存在至少一個備份分片。
備份分片默認不會幫助搜索資料,當ES搜索壓力特別大的時候,備份分片才會幫助搜索資料。
備份的分片必須放在不同的服務器中。
:::

分片:
ES在默認情況下一個索引會被分成五個分片

備份(一般情況下,從分片不會去分擔搜索壓力)

#### 類型Type
:::info
一個索引下,可以建立多個類型。
PS:根據版本不同,類型的建立規則也不同。
:::
Type vs Table

* ES5.X 版本中,一個index下可以建立多個Type
* ES6.X 版本中,一個index下可以建議一個Type
* ES7.X 版本中,一個index下沒有Type
#### 文件Doc
:::info
一個類型下,可以有多的文件,這個文件就類似於Sql表中的多行資料。
:::
document=>一筆一筆資料

#### 屬性Field
::: info
一個文件中,可以包含多個屬性,類似於SQL表中的一行資料存在多個列。
:::
field =>欄位

### 操作ES的RESTful語法
Get請求(查詢):
* 查詢索引內容 : `http://ip:port/index`
* 查詢指定的資料內容: `http://ip:port/index/type/doc_id`
POST請求:
* 查詢,可以在`Content Types`加入json字串來代表查詢條件:`http://ip:port/index/type/_search`
* 修改資料,可以在`Content Types`中指定JSON字串代表修改具體訊息:`http://ip:port/index/type/doc_id/_update`
PUT請求(建立):
* 建立一個索引,需要在`Content Types`中指定索引的訊息:`http://ip:port/index`
* 代表建立索引時,指定索引資料儲存時的屬性的資訊:`http://ip:port/index/type/_mappings`
DELETE 請求:
* 刪除:`http://ip:port/index`
* 刪除指定的資料:`http//ip:port/index/type/doc_id`
### 索引操作
#### 建立一個索引(index)
```json=
#建立一個索引
PUT /person
{
"settings": {
"number_of_shards": 5, //分片數量
"number_of_replicas": 1 //備份數量
}
}
```

#### 查看索引
可以到management > Index Management 查看剛剛建立的索引
正常情況下健康狀況會是綠色,但這裡的健康狀況是黃色因為目前是單機版啟動,所以沒有其他台主機可以做備份

```json=
#查看索引訊息
GET /person
```

#### 刪除索引
```json=
#刪除索引
DELETE /person
```

### ES中Field可以指定的類型
[elasticsearch6.4參考文件](https://www.elastic.co/guide/en/elasticsearch/reference/6.5/mapping-types.html)
資料型態
:::info
- 字串類型:
- text: 一般應用於全文檢索,將當前Field進行分詞。
- keyword:當前Field不會被分詞
- [數字類型](https://www.elastic.co/guide/en/elasticsearch/reference/6.5/number.html):
- long:
- interger:
- short:
- byte:
- double:
- float:
- half_float:精度比float小一半
- scaled_float:根據一個long和scaled來表達一個浮點數,long-345,scaled-100 ->3.45
- 時間類型:
- date:針對時間類型指定具體格式=>"format": "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis"
- 布林類型:
- boolean類型:表達ture & false
- 二進制類型:
- binary類型暫時支持Base64 encode string
- 範圍類型:
- long_range:無須指定具體內容,只需要儲存一個範圍即可,指定gt、lt、gte、lte
- interger_range:
- float_range:
- duble_range
- date_range:
- ip_range:
- 經緯度類型:
- geo_point:用來儲存經緯度的
- IP類型
- ip:可以用來儲存ipv4 or ipv6
- 其他資料型態可以參考官網
:::
### 建立索引、指定資料結構
```json=
PUT /book
{
"settings": {
"number_of_replicas": 1,
"number_of_shards": 5
},
"mappings": {
"novel": {
"properties": {
"name": {
"type": "text",
"analyzer": "ik_max_word",
"index": true
},
"authoor": {
"type": "keyword"
},
"onsale": {
"type": "date",
"format": "yyyy-MM-dd"
}
}
}
}
}
```

* settings:設定分片數量、備份數量
* mappings:設定資料表跟欄位
* 設定Field的時候,可以透過index設定這個Field是否可以當作搜索條件
* 若想指定自行安裝的分詞器時也可以在要被指定的Field中使用analyzer來設定
* date型態可以透過format指定格式樣式
### 資料的操作
:::info
文檔在ES服務中的唯一標示,_index,_type,`_id` 三個內容為組合,鎖定一個文檔,操作添加還是修改。
:::
#### 建立資料
**自動產生id**
```json=
# 建立資料、自動產生id
POST /book/novel
{
"name":"盤龍",
"authoor":"我愛吃西瓜",
"onsale":"2000-01-04"
}
```

這裡可以看出產生的id很詭異
**手動指定id(更推薦)**
```json=
# 建立資料、手動指定id
PUT /book/novel/1
{
"name":"紅樓夢",
"authoor":"曹雪晴",
"onsale":"1985-01-04"
}
```

#### 修改資料
覆蓋式修改
```json=
# 建立資料、手動指定id
PUT /book/novel/1
{
"name":"紅樓夢",
"authoor":"曹雪晴",
"onsale":"1985-01-04"
}
```
doc修改方式(更推薦)
只需要指定需要修改的field對應的值
```json=
#doc修改方式(更推薦)
POST /book/novel/1/_update
{
"doc": {
"authoor":"曹奶奶"
}
}
```

這裡也可以發現版本從1變成了2
#### 刪除資料
```json=
#根據id刪除檔案
DELETE /book/novel/_id
```

## C#操作ElasticSearch
### C#連接ElasticSearch
* NEST或Elasticsearch.Net,其中NEST是對Elasticsearch.NET的封裝,更易使用,建議使用NEST
建立連線
```csharp=
public class ESClient
{
public static ElasticClient GetEsElasticClient()
{
//Uri
var node = new Uri("http://localhost:9200/");
//建立連線字串
var settings = new ConnectionSettings(node);
//建立ElasticClient
var client = new ElasticClient(settings);
return client;
}
}
```
### C# 建立索引 (ES6.5的做法)
建立一個Man的類別
```csharp=
public class Man
{
public string Name { get; set; }
public int Age { get; set; }
public DateTime Birthday { get; set; }
}
```
1. 設定索引的setting
2. 設定索引的結構
3. 透過client連接ES並且執行建立索引
```csharp=
static void Main(string[] args)
{
string exIndex = "preson";
//1.準備關於索引的setting
var settings = new CreateIndexDescriptor(exIndex);
settings.Settings(s => s.NumberOfShards(5).NumberOfReplicas(1));
//2.準備關於索引的結構mappings
settings.Mappings(ms =>
ms.Map<Man>(m=>m.AutoMap<Man>()));
//3.透過clinet去連接ES並執行建立索引
var eSClient = ESClient.GetEsElasticClient();
eSClient.CreateIndex(settings);
}
```
mapping的另一種寫法:
```csharp=
settings.Mappings(ms =>
ms.Map<Man>(m=>m.AutoMap(typeof(Man))));
```

AutoMap 會自動將類別的屬性轉成ES的field
[C#類別轉換成ES的參考資料表](https://www.elastic.co/guide/en/elasticsearch/client/net-api/6.x/auto-map.html)
* string 型別 會轉成 text 和一個子field類型是keyword [詳細至介紹](https://www.elastic.co/guide/en/elasticsearch/client/net-api/6.x/multi-fields.html)
* Int32 型別 會轉成 integer
* UInt16型別 會轉成 integer
* Int16 型別 會轉成 integer
* DateTime型別 會轉成 date
---
也可以將上述的三個動作合併再一起寫
```csharp=
var eSClient = ESClient.GetEsElasticClient();
var createIndexResponse = eSClient.CreateIndex("preson",c=>c.Mappings(ms=>ms
.Map<Man>(m=>m.AutoMap<Man>())));
```
### C# 檢查索引是否存在&刪除索引 (ES6.5的做法)
1. 準備request
2. 通過clinet去執行
3. 輸出結果
檢查索引
```csharp=
string exIndex = "preson";
var eSClient = ESClient.GetEsElasticClient();
//1.準備request
GetIndexRequest request=new GetIndexRequest(exIndex);
//2.通過clinet去檢查
var exists = eSClient.GetIndex(request);
//3.輸出
Console.WriteLine(exists.IsValid);
```
刪除索引
```csharp=
string exIndex = "preson";
var eSClient = ESClient.GetEsElasticClient();
//1.準備request
DeleteIndexRequest request=new DeleteIndexRequest(exIndex);
//2.通過clinet去刪除
var exists = eSClient.DeleteIndex(request);
//3.輸出
Console.WriteLine(exists.IsValid);
```
### C# 操作document (ES6.5的做法)
#### 單筆資料的 增加&修改(覆蓋的方式)
```csharp=
var eSClient = ESClient.GetEsElasticClient();
string exIndex = "preson";
//1.準備資料
var data = new Man()
{
Name = "張景嵐",
Age = 25,
Birthday = new DateTime(1992, 02, 08)
};
//2.準備request
var request = new IndexRequest<Man>(data, exIndex, "man", 1);
//3.通過client執行建立
var result = eSClient.Index(request);
//4.輸出結果
Console.WriteLine(result.IsValid);
```

#### 修改資料以doc的方式
```csharp=
var eSClient = ESClient.GetEsElasticClient();
string exIndex = "preson";
//1.建立一個Map,指定需要修改的內容
Dictionary<string, object> doc = new Dictionary<string, object>();
doc.Add("name","郭雪芙");
//2.準備request
var request = new UpdateRequest<Man,Dictionary<string, object>>(exIndex, "man", 1);
request.Doc = doc;
//3.通過client執行建立
var result = eSClient.Update(request);
//4.輸出結果
Console.WriteLine(result.IsValid);
```

#### 刪除資料
```csharp=
var eSClient = ESClient.GetEsElasticClient();
string exIndex = "preson";
//1.準備request
var request = new DeleteRequest(exIndex,"man",1);
//2.通過client執行建立
var result = eSClient.Delete(request);
//3.輸出結果
Console.WriteLine(result.IsValid);
```
#### 批量增加資料
[官方文件](https://www.elastic.co/guide/en/elasticsearch/client/net-api/1.x/bulk.html)
Man類別
```csharp=
public class Man
{
public int Id { get; set; }
public string Name { get; set; }
public int Age { get; set; }
public DateTime Birthday { get; set; }
}
```
```csharp=
var eSClient = ESClient.GetEsElasticClient();
string exIndex = "preson";
//1.準備多個資料
var data = new List<Man>()
{
new Man(){Id =1,Name = "范冰冰",Age = 22,Birthday = new DateTime(1990,12,05)},
new Man(){Id =2,Name = "劉詩詩",Age = 15,Birthday = new DateTime(1880,10,01)},
new Man(){Id =3,Name = "鬼鬼",Age = 30,Birthday = new DateTime(1988,1,06)},
};
//2.準備Request
var request =new BulkRequest();
request.Operations = data.Select(x => new BulkIndexOperation<Man>(x){Index = exIndex,Type = "man",Id = x.Id } as IBulkOperation).ToList();
//3.通過client執行建立
var result = eSClient.Bulk(request);
//4.輸出
Console.WriteLine(result);
```
#### 批量刪除資料
```csharp=
var eSClient = ESClient.GetEsElasticClient();
string exIndex = "preson";
//1.準備Request
var request = new BulkRequest(exIndex, "man")
{
Operations = new List<IBulkOperation>
{
new BulkDeleteOperation<Man>(1),
new BulkDeleteOperation<Man>(2),
new BulkDeleteOperation<Man>(3),
}
};
//2.通過client執行建立
var result = eSClient.Bulk(request);
//3.輸出
Console.WriteLine(result);
```

## ElasticSearch練習
:::info
索引&類型名稱統一叫做:
String index = "sms_logs_index";
String type = "sms_logs_type";
:::

建立一個類別,並透過attribute-mapping [參考網址](https://www.elastic.co/guide/en/elasticsearch/client/net-api/6.x/attribute-mapping.html)
官網有提到如果用attribute-mapping必須使用.AutoMap()
> When you use attributes, you must also call .AutoMap() for the attributes to be applied.
```csharp=
[ElasticsearchType(Name = "sms_logs_type")]
public class SmsLog
{
/// <summary>
/// 建立時間
/// </summary>
[Date(Name= "createDate")]
public DateTime CreateDate { get; set; }
/// <summary>
/// 發送時間
/// </summary>
[Date(Name = "sendDate")]
public DateTime SendDate { get; set; }
/// <summary>
/// 發送長號碼 如 16092389287811
/// </summary>
[Text(Name = "longCode")]
public string LongCode { get; set; }
/// <summary>
/// 手機號碼 如 0980685666
/// </summary>
[Text(Name = "mobile")]
public string Mobile { get; set; }
/// <summary>
/// 公司名稱
/// </summary>
[Text(Name = "corpName",Analyzer = "ik_max_word")]
public string CorpName { get; set; }
/// <summary>
/// 簡訊內容
/// </summary>
[Text(Name = "smsContent", Analyzer = "ik_max_word")]
public string SmsContent { get; set; }
/// <summary>
/// 簡訊發送狀態 0 成功 1 失败 integer
/// </summary>
[PropertyName("state")]
public int State { get; set; }
/// <summary>
/// 電信商編號1中華電信2遠傳3台灣大哥大 integer
/// </summary>
[PropertyName("operatorid")]
public int Operatorid { get; set; }
/// <summary>
/// 城市
/// </summary>
[Text(Name = "county")]
public string County { get; set; }
/// <summary>
/// 伺服器IP
/// </summary>
[Text(Name = "ipAddr")]
public string IpAddr { get; set; }
/// <summary>
/// 簡訊狀態報告傳回時間
/// </summary>
[PropertyName("replyTotal")]
public int ReplyTotal { get; set; }
/// <summary>
/// 費用
/// </summary>
[PropertyName("fee")]
public int Fee { get; set; }
}
```
**得到的json樣式**
```json=
{
"mapping": {
"sms_logs_type": {
"properties": {
"corpName": {
"type": "text",
"analyzer": "ik_max_word"
},
"county": {
"type": "text"
},
"createDate": {
"type": "date",
},
"fee": {
"type": "integer"
},
"ipAddr": {
"type": "text"
},
"longCode": {
"type": "text"
},
"mobile": {
"type": "text"
},
"operatorid": {
"type": "integer"
},
"replyTotal": {
"type": "integer"
},
"sendDate": {
"type": "date",
},
"smsContent": {
"type": "text",
"analyzer": "ik_max_word"
},
"state": {
"type": "integer"
}
}
}
}
}
```
主程式中在執行
```csharp=
var createIndexResponse = eSClient.CreateIndex("sms_logs_index", c => c.Mappings(ms => ms.Map<SmsLog>(m => m.AutoMap<SmsLog>())));
Console.WriteLine(createIndexResponse.ToString());
```
準備插入的資料
```csharp=
private static string GetPhone()
{
string mobile = "09";
for (int i = 0; i < 8; i++)
{
Thread.Sleep(10);
mobile += new Random(DateTime.Now.Millisecond).Next(0, 9).ToString();
}
return mobile;
}
```
```csharp=
//1.準備資料
var longcode = "1008687";
var companies = new List<string>
{
"台積電",
"統一企業集團",
"大同集團",
"長榮航空",
"旺旺集團",
"星光遠航",
"潤泰企業集團",
"鼎泰豐",
"東森媒體集團"
};
var country = new List<string>
{
"台北市",
"新北市",
"基隆",
"宜蘭縣",
"宜蘭市",
"新竹市",
"台南縣",
"台中市",
"高雄市"
};
var smsContents = new List<string>
{
"思辨 事實 是 大便",
"願快樂揮之不去,讓機遇只爭朝夕,願身體健康如一,讓好運春風化雨,願幸福如期而至,讓情誼日積月累,願片言表我心語,願春節你闔家幸福,事事稱意!",
"辭舊迎新,新年問好。祝君:新年新面貌、新年新氣象、新年新起點、新年新開始、新年新心情、新年新運程、新年新局面、新年新收穫、新年新跨越!辭舊迎新,新年問好。",
"福氣多多,快樂連連,萬事圓圓,微笑甜甜,一帆風順,二龍騰飛,三羊開泰,四季平安,五福臨門,六六大順,七星高照,八方來財,九九同心,十全十美!春節愉快!",
"辭舊迎新要有底,將快樂進行到底,將健康保持到底,將幸福堅持到底,將吉祥陪伴到底,將平安延續到底,將祝福送到心底:祝新年快樂!",
"鍵盤敲敲,拇指點點,短信寫寫,祝福傳傳,鈴聲響響,屏幕閃閃,友情深深,心頭暖暖,除夕鬧鬧,日曆翻翻,心情美美,日子甜甜,祝愿聲聲,共賀新年!",
"零時的鐘聲響徹天涯,新年的列車準時出發,它托去一個難忘的歲月,應來了又一個火紅的年華,祝您新年快樂,萬事如意,心想事成!",
"願歡快的歌聲,時刻縈繞你;願歡樂年華,永遠伴隨您;願歡樂的祝福,永遠追隨您。祝福您:春節愉快,身體健康,闔家歡樂,萬事順意!",
"願你:大財、小財、意外財,財源滾滾;親情、友情、愛情,情情如意官運;福運、財運、桃花運,運運亨通。新春到來之際,送上我最真摯的祝福:春節快樂!",
"願你天天順,時時順,分分順,秒秒順,月月順,年年順,愛情順,事業順,身體順,生活順,總之一切都能順,最後還要祝你新年順永遠順!",
"牛轉乾坤、牛運當頭、牛運亨通、牛年財到、牛氣沖天、牛運旺旺來、牛年吉祥、牛年大吉大利、牛年富貴、牛年紅運、牛年牛市、牛年開泰、牛年安康、牛轉新機"
};
var operations = new List<IBulkOperation>();
for (int i = 0; i < 16; i++)
{
var s1 = new SmsLog()
{
CreateDate = new DateTime(2020, 10, 16),
SendDate = new DateTime(2020, 10, 16),
LongCode = longcode + i,
Mobile = GetPhone(),
CorpName = companies[i % 9],
SmsContent = smsContents[i % 9],
State = i % 2,
Operatorid = i % 3,
County = country[i % 9],
IpAddr = "127.0.0." + i,
ReplyTotal = i * 3,
Fee = i * 6
};
operations.Add(new BulkIndexOperation<SmsLog>(s1) { Id = i + 1 });
}
//2.準備Request
var request = new BulkRequest("sms_logs_index", "sms_logs_type")
{
Operations = operations
};
//3.通過client執行建立
var result = eSClient.Bulk(request);
//4.輸出
Console.WriteLine(result.IsValid);
```
## ElasticSearch的各種查詢
### term & terms 查詢
#### [term查詢](https://www.elastic.co/guide/en/elasticsearch/client/net-api/6.x/query-usage.html)
:::info
term的查询是代表完全匹配,搜尋之前不會對你搜尋的關鍵字進行分詞,直接拿 關鍵字 去文件的分詞庫中匹配内容
:::
```json=
# term查詢
POST /sms_logs_index/sms_logs_type/_search
{
"from": 0,
"size": 5,
"query": {
"term": {
"county": {
"value": "台北市"
}
}
}
}
```
C#執行
```csharp=
var eSClient = ESClient.GetEsElasticClient();
//1.建立Request & 指定查詢條件
var request = new SearchRequest<SmsLog>("sms_logs_index", "sms_logs_type")
{
From = 0,
Size = 20,
Query = new TermQuery
{
Field = "county",
Value = "台北市"
}
};
//2.執行查詢
var result =eSClient.Search<SmsLog>(request);
//3.輸出結果 __source的資料
var list = new List<SmsLog>();
foreach (var item in result.Hits)
{
list.Add(item.Source);
Console.WriteLine(item.Source.CorpName);
}
```
#### [terms查詢](https://www.elastic.co/guide/en/elasticsearch/client/net-api/7.x/terms-query-usage.html)
:::info
terms 和 term 查询的機制一样,搜尋之前不會對你搜尋的關鍵字進行分詞,直接拿 關鍵字 去文件分詞庫中匹配内容
terms:是針對一個字串包含多個數值
term : where county = 台北市
terms: where county = 台北市 or county = 台中市 (類似于mysql 中的 in)
也可針對 text, 只是在分詞庫中查询的時候不會進行分詞
:::
```json=
# terms查詢
POST /sms_logs_index/sms_logs_type/_search
{
"from": 0,
"size": 5,
"query": {
"terms": {
"county": [
"台北市",
"台中市"
]
}
}
}
```
這裡的Size是每一次查詢回傳得數量,FROM是從第幾筆資料開始查詢
```csharp=
//1.建立Request & 指定查詢條件
var request = new SearchRequest<SmsLog>("sms_logs_index", "sms_logs_type")
{
From = 0,
Size = 5,
Query = new TermsQuery
{
Field = "county",
Terms = new List<object>()
{
"台北市",
"台中市"
}
}
};
//2.執行查詢
var result =eSClient.Search<SmsLog>(request);
//3.輸出結果 __source的資料
var list = new List<SmsLog>();
foreach (var item in result.Hits)
{
list.Add(item.Source);
Console.WriteLine(item.Source.CorpName);
}
```
### [match查詢](https://www.elastic.co/guide/en/elasticsearch/client/net-api/7.x/multi-match-usage.html)
:::info
match 查詢屬於高級查詢,會根據你查詢的字串類型不一樣,採用不同的查詢方式:
- 查詢的是日期或者數字,它會將基於你查詢得字串內容轉換為同等的日期或數字
- 如果查詢的內容是一个不能被分詞的内容(keyword),match 不會將你指定的關鍵字進行分詞
- 如果查詢的内容是一个可以被分詞的内容(text),match 查詢會將你指定的内容根據一定的方式進行分詞,去分詞庫中匹配指定的內容
**match 查询,實際上底層就是多個term 查询,將多個term查詢的结果给你封裝到一起**
:::
#### math_all
:::info
查詢全部内容,不指定查詢條件
:::
```json=
POST /sms_logs_index/sms_logs_type/_search
{
"query":{
"match_all": {}
}
}
```
C#執行
```csharp=
//1.建立Request & 指定查詢條件
var request = new SearchRequest<SmsLog>("sms_logs_index", "sms_logs_type")
{
From = 0,
Size = 20,//默認值是10
Query = new MatchAllQuery()
};
//2.執行查詢
var result =eSClient.Search<SmsLog>(request);
//3.輸出結果 __source的資料
var list = new List<SmsLog>();
foreach (var item in result.Hits)
{
list.Add(item.Source);
Console.WriteLine(item.Source.CorpName);
}
```
#### match 查询
:::info
指定一個field 作為查詢條件
:::
```json=
#match 查询
POST /sms_logs_index/sms_logs_type/_search
{
"query": {
"match": {
"smsContent": "排時間"
}
}
}
```
```csharp=
//1.建立Request & 指定查詢條件
var request = new SearchRequest<SmsLog>("sms_logs_index", "sms_logs_type")
{
From = 0,
Size = 20,//默認值是10
Query = new MatchQuery()
{
Field = "smsContent",
Query = "排時間"
}
};
//2.執行查詢
var result =eSClient.Search<SmsLog>(request);
//3.輸出結果 __source的資料
var list = new List<SmsLog>();
foreach (var item in result.Hits)
{
list.Add(item.Source);
Console.Write(item.Id +" ");
Console.WriteLine(item.Source.CorpName);
}
```
#### bool match 查询
:::info
基於一個field 匹配的内容,按照 and 或者or的方式連接
:::
> 既包含認為 也必須包含 最壞
```json=
#布林match查询
POST /sms_logs_index/sms_logs_type/_search
{
"query": {
"match": {
"smsContent": {
"query": "認為 最壞",
"operator": "and"
}
}
}
}
```
C#
```csharp=
//1.建立Request & 指定查詢條件
var request = new SearchRequest<SmsLog>("sms_logs_index", "sms_logs_type")
{
From = 0,
Size = 20,//默認值是10
Query = new MatchQuery()
{
Field = "smsContent",
Query = "認為 最壞",
Operator = Operator.And
}
};
//2.執行查詢
var result =eSClient.Search<SmsLog>(request);
//3.輸出結果 __source的資料
var list = new List<SmsLog>();
foreach (var item in result.Hits)
{
list.Add(item.Source);
Console.Write(item.Id +" ");
Console.WriteLine(item.Source.CorpName);
}
}
```
> 既包含認為 或者包含 最壞
```json=
#布林match查询
POST /sms_logs_index/sms_logs_type/_search
{
"query": {
"match": {
"smsContent": {
"query": "認為 最壞",
"operator": "or"
}
}
}
}
```
C#
```csharp=
//1.建立Request & 指定查詢條件
var request = new SearchRequest<SmsLog>("sms_logs_index", "sms_logs_type")
{
From = 0,
Size = 20,//默認值是10
Query = new MatchQuery()
{
Field = "smsContent",
Query = "認為 最壞",
Operator = Operator.Or
}
};
//2.執行查詢
var result =eSClient.Search<SmsLog>(request);
//3.輸出結果 __source的資料
var list = new List<SmsLog>();
foreach (var item in result.Hits)
{
list.Add(item.Source);
Console.Write(item.Id +" ");
Console.WriteLine(item.Source.CorpName);
}
}
```
#### multi_match
:::info
match 針對一個field 做搜索,multi_math 針對多個field 進行搜索,多個field對應一個文本。
:::
```json=
#multi_math 查詢
POST /sms_logs_index/sms_logs_type/_search
{
"query":{
"multi_match": {
"query": "台北市",
"fields": ["county","smsContent"]
}
}
}
```
C#
```csharp=
//1.建立Request & 指定查詢條件
var request = new SearchRequest<SmsLog>("sms_logs_index", "sms_logs_type")
{
From = 0,
Size = 20,//默認值是10
Query = new MultiMatchQuery()
{
Query = "台北市",
Fields = new Field("county").And("smsContent")
}
};
//2.執行查詢
var result =eSClient.Search<SmsLog>(request);
//3.輸出結果 __source的資料
var list = new List<SmsLog>();
foreach (var item in result.Hits)
{
list.Add(item.Source);
Console.Write(item.Id +" ");
Console.WriteLine(item.Source.CorpName);
}
```
### 其他查詢
#### id 查詢
```json=
#id 查詢
GET /sms_logs_index/sms_logs_type/1
```
```csharp=
//1.建立Request & 指定查詢條件
var request = new GetRequest("sms_logs_index", "sms_logs_type", "1");
//2.執行查詢
var result =eSClient.Get<SmsLog>(request);
//3.輸出結果 __source的資料
var list = new List<SmsLog>();
list.Add(result.Source);
Console.Write(result.Id + " ");
Console.WriteLine(result.Source.CorpName);
```
#### id 查詢
:::info
根據多個id 查詢,類似 mysql 中的 where in (id1,id2...)
:::
```json=
#ids 查询
POST /sms_logs_index/sms_logs_type/_search
{
"query": {
"ids": {
"values": ["1","2","3"]
}
}
}
```
```csharp=
//1.建立Request & 指定查詢條件
var request = new SearchRequest<SmsLog>("sms_logs_index", "sms_logs_type")
{
From = 0,
Size = 20,//默認值是10
Query = new IdsQuery()
{
Values = new List<Id>()
{
new Id("1"),
new Id("2"),
new Id("3")
}
}
};
//2.執行查詢
var result =eSClient.Search<SmsLog>(request);
//3.輸出結果 __source的資料
var list = new List<SmsLog>();
foreach (var item in result.Hits)
{
list.Add(item.Source);
Console.Write(item.Id + " ");
Console.WriteLine(item.Source.CorpName);
}
```
#### prefix 查询
:::info
前缀查詢,可以通過一個關鍵字去指定一個field 的前缀,從而查詢到指定文件
:::
```json=
#prefix 查询
POST /sms_logs_index/sms_logs_type/_search
{
"query": {
"prefix": {
"corpName": {
"value": "台"
}
}
}
}
```
**在這裡什麼都查不到 和上面的prefix 做比較**
```json=
#match 查詢
POST /sms_logs_index/sms_logs_type/_search
{
"query": {
"match": {
"corpName": "台"
}
}
}
```
```csharp=
//1.建立Request & 指定查詢條件
var request = new SearchRequest<SmsLog>("sms_logs_index", "sms_logs_type")
{
From = 0,
Size = 20,//默認值是10
Query = new PrefixQuery()
{
Field = "corpName",
Value = "台"
}
};
//2.執行查詢
var result =eSClient.Search<SmsLog>(request);
//3.輸出結果 __source的資料
var list = new List<SmsLog>();
foreach (var item in result.Hits)
{
list.Add(item.Source);
Console.Write(item.Id + " ");
Console.WriteLine(item.Source.CorpName);
}
```
#### fuzzy 查询
:::info
模糊查詢,我们可以输入一個大概的字串,ES 可以根據输入的大概去匹配内容。查尋结果不穩定
:::
prefix_length=>用來指定前面幾個字,是不允許不一樣
```json=
#fuzzy 查询
POST /sms_logs_index/sms_logs_type/_search
{
"query": {
"fuzzy": {
"county": {
"value": "台北市",
"prefix_length": 1
}
}
}
}
```
```csharp=
//1.建立Request & 指定查詢條件
var request = new SearchRequest<SmsLog>("sms_logs_index", "sms_logs_type")
{
From = 0,
Size = 20,//默認值是10
Query = new FuzzyQuery()
{
Field = "county",
Value = "台北市",
PrefixLength = 1
}
};
//2.執行查詢
var result =eSClient.Search<SmsLog>(request);
//3.輸出結果 __source的資料
var list = new List<SmsLog>();
foreach (var item in result.Hits)
{
list.Add(item.Source);
Console.Write(item.Id + " ");
Console.WriteLine(item.Source.County);
}
```
#### wildcard 查询
:::info
通配查詢,同mysql中的like 是一樣的,可以在查詢时,在字串中指定通配符『*』和佔位符『?』
:::
```json=
#wildcard 查詢
POST /sms_logs_index/sms_logs_type/_search
{
"query": {
"wildcard": {
"county": {
"value": "台北*"
}
}
}
}
```
```json=
#wildcard 查詢
POST /sms_logs_index/sms_logs_type/_search
{
"query": {
"wildcard": {
"county": {
"value": "台北?"
}
}
}
}
```
```csharp=
//1.建立Request & 指定查詢條件
var request = new SearchRequest<SmsLog>("sms_logs_index", "sms_logs_type")
{
From = 0,
Size = 20,//默認值是10
Query = new WildcardQuery()
{
Field = "county",
Value = "台北*",
}
};
//2.執行查詢
var result =eSClient.Search<SmsLog>(request);
//3.輸出結果 __source的資料
var list = new List<SmsLog>();
foreach (var item in result.Hits)
{
list.Add(item.Source);
Console.Write(item.Id + " ");
Console.WriteLine(item.Source.County);
}
```
#### [Rang 查询](https://www.elastic.co/guide/en/elasticsearch/client/net-api/7.x/numeric-range-query-usage.html)
:::info
範圍查詢,只針對數值類型,對一個field 進行大於或者小於的範圍指定
:::
查詢fee價錢在6~18之間的資料
```json=
#rang 查詢
POST /sms_logs_index/sms_logs_type/_search
{
"query": {
"range": {
"fee": {
"gte": 6,
"lte": 18
}
}
}
}
```
C#版:
:memo: Field要注意,大小寫是不一樣得
```csharp=
//1.建立Request & 指定查詢條件
var request = new SearchRequest<SmsLog>("sms_logs_index", "sms_logs_type")
{
From = 0,
Size = 20,//默認值是10
Query = new NumericRangeQuery()
{
Field = "fee",
GreaterThanOrEqualTo = 6,
LessThanOrEqualTo = 18
}
};
//2.執行查詢
var result =eSClient.Search<SmsLog>(request);
//3.輸出結果 __source的資料
var list = new List<SmsLog>();
foreach (var item in result.Hits)
{
list.Add(item.Source);
Console.Write(item.Id + " ");
Console.WriteLine(item.Source.County);
}
}
```
#### [Regexp 查询](https://www.elastic.co/guide/en/elasticsearch/client/net-api/7.x/regexp-query-usage.html)
:::info
正則查詢,通過編寫你編寫得正則表達式去匹配內容
Ps:prefix wildcard fuzzy 和regexp 查詢效率比較低,在要求效率比較高時,避免使用
:::
```json=
#regexp 查詢
POST /sms_logs_index/sms_logs_type/_search
{
"query": {
"regexp": {
"mobile": "0964[0-9]{6}"
}
}
}
```
C# 版本:
```csharp=
//1.建立Request & 指定查詢條件
var request = new SearchRequest<SmsLog>("sms_logs_index", "sms_logs_type")
{
From = 0,
Size = 20,//默認值是10
Query = new RegexpQuery()
{
Field = "mobile",
Value = "0964[0-9]{6}"
}
};
//2.執行查詢
var result =eSClient.Search<SmsLog>(request);
//3.輸出結果 __source的資料
var list = new List<SmsLog>();
foreach (var item in result.Hits)
{
list.Add(item.Source);
Console.Write(item.Id + " ");
Console.WriteLine(item.Source.County);
}
```
### 深分頁 scroll
:::info
ES 對from +size對是有限制的,什麼限制呢?
from +size 之和 不能大於1W,超過後 效率會十分低
原理:
- from+size ES查詢數據的方式,
1. 第一步將用户指定的關鍵字進行分詞,
2. 第二步將詞彙去分詞庫中進行搜索,得到多个文档id,
3. 第三步去各個分片中拉取資料,耗時相對比較長
4. 第四步根據score 將資料進行排序, 耗時相對比較長
5. 第五步根據from 和size 的值 將部分資料捨棄,
6. 第六步,返回结果。
- scroll +size ES 查詢資料的方式
1. 第一步將用戶指定的關鍵字進行分詞,
2. 第二步將詞彙去分詞庫中進行搜索,得到多個文件id,
3. 第三步將文件的id放在一个上下文中
4. 第四步根據指定的size去ES中搜索指定個數資料,拿完數據的文件id,會從上下文中移除
5. 第五步如果需要下一頁的資料,直接去ES的上下文中找後續内容。
6. 第六步循環第四步和第五步
scroll 不適合做立即查询,因為它會先把資料存到記憶體當中
如果沒有使用scroll ES在查詢下一個分頁的時候會從第一步執行到第六步,
但使用scroll的話每一次查詢下一個分頁的時候,是從第四步到第六步
:::
```json=
#scroll 查詢,傳回第一頁的資料,並將文件id信息存放在ES上下文中,並指定存活時間
POST /sms_logs_index/sms_logs_type/_search?scroll=1m
{
"query": {
"match_all": {}
},
"size": 2,
"sort": [
{
"fee": {
"order": "desc"
}
}
]
}
#根據scroll 查詢下一頁的資料
POST _search/scroll
{
"scroll_id":"DnF1ZXJ5VGhlbkZldGNoBQAAAAAAAwUbFmVsZS02ejJaUk5lTkwzVlFkMGMwS1EAAAAAAAMFGhZlbGUtNnoyWlJOZU5MM1ZRZDBjMEtRAAAAAAADBRwWZWxlLTZ6MlpSTmVOTDNWUWQwYzBLUQAAAAAAAwUdFmVsZS02ejJaUk5lTkwzVlFkMGMwS1EAAAAAAAMFHhZlbGUtNnoyWlJOZU5MM1ZRZDBjMEtR",
"scroll":"1m"
}
#刪除scroll上下文中的資料
DELETE _search/scroll/DnF1ZXJ5VGhlbkZldGNoBQAAAAAAAwUbFmVsZS02ejJaUk5lTkwzVlFkMGMwS1EAAAAAAAMFGhZlbGUtNnoyWlJOZU5MM1ZRZDBjMEtRAAAAAAADBRwWZWxlLTZ6MlpSTmVOTDNWUWQwYzBLUQAAAAAAAwUdFmVsZS02ejJaUk5lTkwzVlFkMGMwS1EAAAAAAAMFHhZlbGUtNnoyWlJOZU5MM1ZRZDBjMEtR
```

[sort](https://www.elastic.co/guide/en/elasticsearch/client/net-api/7.x/sort-usage.html)
C#版本:
```csharp=
//1.建立Request & 指定查詢條件
var request = new SearchRequest<SmsLog>("sms_logs_index", "sms_logs_type")
{
From = 0,
Size = 2,//默認值是10
Sort = new List<ISort>()
{
new SortField() { Field = "fee", Order = SortOrder.Descending }
},
Scroll = Time.MinusOne//指定scroll 過期時間
};
//2.執行查詢
var response = eSClient.Search<SmsLog>(request);
Console.WriteLine("-------------第一筆資料--------------");
var list = new List<SmsLog>();
//3.輸出結果 __source的資料
foreach (var item in response.Hits)
{
list.Add(item.Source);
Console.Write(item.Id + " ");
Console.WriteLine(item.Source.County);
}
while (true)
{
//5.建立scroll request,指定scroll Id 以及有效時間
var scrollRequest = new ScrollRequest(response.ScrollId, new Time(1));
//6.執行查詢,返回查詢結果
var scroll = eSClient.Scroll<SmsLog>(scrollRequest);
//7.判斷是否查詢到資料,查詢到輸出
if (scroll.Documents.Any())
{
Console.WriteLine("-------------下一頁資料------------------");
var searchHits = scroll.Hits;
foreach (var item in searchHits)
{
list.Add(item.Source);
Console.WriteLine(item.Source.County);
}
}
else
{
//8.沒有資料,結束
Console.WriteLine("----------------结束---------------------");
break;
}
}
//刪除scroll上下文中的資料
//1.建立clearScrollRequest 並指定ScrollId
var clearScrollRequest = new ClearScrollRequest(response.ScrollId);
//2.透過client 執行删除scroll
var clearScrollResponse = eSClient.ClearScroll(clearScrollRequest);
//3.輸出結果
Console.WriteLine("刪除scroll: " + clearScrollResponse.IsValid);
```
### delete-by-query
:::info
如果要刪除指定條件的資料,可以使用delete-by-query
PS:如果你要刪除的內容,是index下的大部分的資料,推薦建立一個新的index,然后把保留的資料内容,加入到全新的索引
:::
```json=
#Delet-by-query 刪除
POST /sms_logs_index/sms_logs_type/_delete_by_query
{
"query": {
"range": {
"fee": {
"lt": 6
}
}
}
}
```

```csharp=
//1.建立Request & 指定查詢條件
var resquest = new DeleteByQueryRequest<SmsLog>("sms_logs_index", "sms_logs_type")
{
Query = new QueryContainer(
new NumericRangeQuery()
{
Field = "fee",
GreaterThanOrEqualTo = 6,
}
)
};
//2.通過client執行建立
var response = eSClient.DeleteByQuery(resquest);
//3.輸出結果
Console.WriteLine(response.IsValid);
```
### [複合查詢](https://www.elastic.co/guide/en/elasticsearch/client/net-api/7.x/bool-queries.html)
:::info
複合過濾器,將你的多個查詢條件 以一定的邏輯組合再一起,
must:所有條件組合在一起,表示 and 的意思
must_not: 將must_not中的條件,全部都不能匹配,表示not的意思
should:所有條件用should 組合再一起,表示or 的意思
:::
#### bool查詢
```json=
#縣市是台北市或高雄市
# operatorid不能是2
#smsContent 包含 事實 和 思辨
POST /sms_logs_index/sms_logs_type/_search
{
"query": {
"bool": {
"should": [
{
"term": {
"county": "台北市"
}
},
{
"term": {
"county": "高雄市"
}
}
],
"must_not": [
{
"term": {
"operatorid": "2"
}
}
],
"must": [
{
"match": {
"smsContent": "事實"
}
},
{
"match": {
"smsContent": "思辨"
}
}
]
}
}
}
```
C#版本
```csharp=
//1.建立Request & 指定查詢條件
var resquest = new SearchRequest<SmsLog>("sms_logs_index", "sms_logs_type")
{
Size = 20,
Query = (new TermQuery { Field = "county", Value = "台北市" } ||
new TermQuery { Field = "county", Value = "高雄市" } ) &
!new TermQuery { Field = "operatorid", Value = "2" } &
new MatchQuery { Field = "smsContent", Query = "事實" } &
new MatchQuery { Field = "smsContent", Query = "思辨" }
};
//2.通過client執行建立
var response = eSClient.Search<SmsLog>(resquest);
//3.輸出結果
Console.WriteLine(response.IsValid);
foreach (var item in response.Hits)
{
Console.WriteLine(item.Id);
Console.WriteLine(item.Source.County);
}
```
#### boosting 查询(影響分數)
:::info
boosting查詢,可以幫助我們去影響查詢後的分數(score)
- positive: 只有匹配上positive 查詢的内容,才會被放到返回到結果集合中
- negative: 如果匹配上了positive 也匹配上了negative,就可以降低資料的分數(score)
- negative_boost:指定係數,必須小於1=>如果匹配上上述兩個條件分數就會乘上這個係數
關於查詢時,分數如何計算的:
- 搜索關鍵字在資料中出現的頻率越高,分數越高
- 指定的資料內容越短,分數越高。
- 我們在搜尋時,指定的關鍵字也會被分詞,這個被分詞的內容,被分詞庫匹配的個數越多,分數越高。
:::
```json=
#boosting 查询
POST /sms_logs_index/sms_logs_type/_search
{
"query": {
"boosting": {
"positive": {
"match": {
"smsContent": "思辨"
}
},
"negative": {
"match": {
"smsContent": "損失"
}
},
"negative_boost": 0.2
}
}
}
```
```csharp=
//1.建立Request & 指定查詢條件
var resquest = new SearchRequest<SmsLog>("sms_logs_index", "sms_logs_type")
{
Size = 20,
Query = new BoostingQuery
{
PositiveQuery = new MatchQuery()
{
Field = "smsContent",
Query = "思辨"
},
NegativeQuery = new MatchQuery()
{
Field = "smsContent",
Query = "損失"
},
NegativeBoost = 0.2
}
};
//2.通過client執行建立
var response = eSClient.Search<SmsLog>(resquest);
//3.輸出結果
Console.WriteLine(response.IsValid);
foreach (var item in response.Hits)
{
Console.WriteLine(item.Id);
Console.WriteLine(item.Source.County);
}
```
#### filter 查询
:::info
query 查詢:根據你查詢的條件,去計算資料的匹配度得到一個分數,並根據分數作排序,不會做快取的。
filter 查詢:根據查詢條件去查詢document,不去計算分數,而且filter會對經常被過濾的資料進行快取。
:::
```json=
#filter 查询
POST /sms_logs_index/sms_logs_type/_search
{
"query": {
"bool": {
"filter": [
{
"term": {
"corpName": "台"
}
},
{
"range":{
"fee":{
"lte": 400
}
}
}
]
}
}
}
```
#### Highlight 查询
:::info
Highlight查詢就是用戶輸入的關鍵字,以一定的特殊樣式展示給用戶看,讓用戶知道為什麼是這個結果被搜索出來
Highlight展示的資料,本身就是document中的一個field,單獨將field以highlight的形式返回給用戶
ES提共了一個highlight 屬性,他和query 同級別。
- frament_size: 指定highlight資料展示多長的字串回來
- pre_tags:指定前綴標籤`<front color="red">`
- post_tags:指定後綴標籤 `</font>`
:::
```json=
#highlight 查询
POST /sms_logs_index/sms_logs_type/_search
{
"query": {
"match": {
"smsContent": "思辨"
}
},
"highlight": {
"fields": {
"smsContent":{}
},
"pre_tags":"<font color='red'>",
"post_tags":"</font>",
"fragment_size":10
}
}
```
#### 聚合查詢
:::info
ES的聚合查詢和mysql 的聚合查詢類似,ES的聚合查詢相比mysql 要強大的多。ES提供的統計資料的方式多種多樣。
:::
聚合查詢與一般查詢語法略為不同,以下是基本格式:
```json=
#ES 聚合查询的RSTFul 语法
POST /index/type/_search
{
"aggs":{
"(名字)agg":{
"agg_type":{
"属性":"值"
}
}
}
}
```
##### cardinality 去除重複資料的記數的聚合查询
:::info
去重計數,cardinality 統計document中的一個指定的field,除去重複的資料,統計一共有多少條
:::
譬如:
|Name |居住地 |
|-----|--------|
|張景嵐|台北市 |
|劉詩詩|台南市 |
|郭雪芙|高雄市 |
|林依晨|台南市 |
|溫妮 |台南市 |
這時候如果使用cardinality統計居住地 計算結果會是 3
```json=
# 去重計算 查询 county
POST /sms_logs_index/sms_logs_type/_search
{
"aggs": {
"provinceAgg": {
"cardinality": {
"field": "county"
}
}
}
}
```
##### 範圍統計
:::info
統計一個範圍出現的資料筆數,譬如,針對某一個field 的值再0~100,100~200,200~300 之間資料出現的各數分別數少
範圍統計 可以針對 普通的數值,針對時間類型,針對ip類型都可以響應。
數值 rang
時間 date_rang
IP ip_rang
:::
以下會統計這三個範圍:
* 小於30以下的資料
* 大於等於30 小於60的資料
* 大於等於60的資料
```json=
#針對數值 from 的意思是 大於等於 ,to 是小於
POST /sms_logs_index/sms_logs_type/_search
{
"aggs": {
"agg": {
"range": {
"field": "fee",
"ranges": [
{
"to": 30
},
{
"from": 30,
"to": 60
},
{
"from": 60
}
]
}
}
}
}
```
```json=
#時間方式統計
POST /sms_logs_index/sms_logs_type/_search
{
"aggs": {
"agg": {
"date_range": {
"field": "sendDate",
"format": "yyyy",
"ranges": [
{
"to": "2000"
},{
"from": "2000"
}
]
}
}
}
}
```
```json=
#ip 方式 範圍統計
POST /sms_logs_index/sms_logs_type/_search
{
"aggs": {
"agg": {
"ip_range": {
"field": "ipAddr",
"ranges": [
{
"to": "127.0.0.8"
},
{
"from": "127.0.0.8"
}
]
}
}
}
}
```
##### 統計聚合
:::info
可以查指定的field 的最大值,最小值,平均值,平方和...
使用 extended_stats
:::
這裡的agg 可自由命名
```json=
#統計聚合查詢 extended_stats
POST /sms_logs_index/sms_logs_type/_search
{
"aggs": {
"agg": {
"extended_stats": {
"field": "fee"
}
}
}
}
```
輸出結果:

##### 其他查詢
[官方的document](https://www.elastic.co/guide/en/elasticsearch/reference/6.8/search-aggregations-metrics-weight-avg-aggregation.html)
### 地圖經緯度查詢
#### 準備資料
請注意type要使用geo_point
```json=
#建立一個經緯度索引,指定一個name ,一個location
PUT /map
{
"settings": {
"number_of_shards": 5,
"number_of_replicas": 1
},
"mappings": {
"map":{
"properties":{
"name":{
"type":"text"
},
"location":{
"type":"geo_point"
}
}
}
}
}
#加入測試數據
PUT /map/map/1
{
"name":"101購物中心",
"location":{
"lon": 121.5765668,
"lat": 25.0299653
}
}
PUT /map/map/2
{
"name":"國立國父紀念館",
"location":{
"lon": 121.5757755,
"lat": 25.0347843
}
}
PUT /map/map/3
{
"name":"Miramar美麗華摩天輪",
"location":{
"lon": 121.5835182,
"lat": 25.025217
}
}
```
#### ES 的地圖搜索方式
:::info
geo_distance :直線距離搜索方式
geo_bounding_box: 以兩個點圍成一個矩形,獲取矩形內的資料
geo_polygon:以多個點,圍成一個多邊形,獲取多邊形內的全部資料
:::
#### 基於RESTFul 實現地圖搜索
**geo_distance**
查詢指定地點為圓心,周遭2千公尺內的物件資料
```json=
#geo_distance
POST /map/map/_search
{
"query": {
"geo_distance":{
"location":{
"lon":121.5660955,
"lat":25.0383058
},
"distance":2000,
"distance_type":"arc"
}
}
}
```

以兩個點圍成一個矩形,獲取矩形內的資料
```json=
#geo_bounding_box
POST /map/map/_search
{
"query":{
"geo_bounding_box":{
"location":{
"top_left":{
"lon": 121.5427135,
"lat": 25.0490381
},
"bottom_right":{
"lon": 121.584229,
"lat": 25.027229
}
}
}
}
}
```
以多個點,圍成一個多邊形,獲取多邊形內的全部資料
```json=
#geo_polygon
POST /map/map/_search
{
"query":{
"geo_polygon":{
"location":{
"points":[
{
"lon": 121.5427135,
"lat": 25.0490381
},
{
"lon": 121.5795951,
"lat": 25.0370384
},
{
"lon": 121.584229,
"lat": 25.027229
}
]
}
}
}
}
```