--- tags: Elasticsearch學習 --- # ElasticSearchr學習 ## ElasticSearchr 介紹 ### 引言 當我們在購物網站搜尋=>"手機殼",它如果在SQL中執行可能會使用這樣的語法 ```sql= select *form item where name like %手機殼% ``` 但是如果用SQL搜尋使用%索引會失效,效率是極低的 ![](https://i.imgur.com/7nUJcH6.png) 這時候如果再把關鍵字改成搜尋=>"殼手機",並且還是搜尋到了,搜尋的方式疑似會將我們輸入的關鍵字 拆分開來,再去匹配商品內容,以我們學習到的sql是無法做到這件事情的 ![](https://i.imgur.com/WrMQbdL.png) ElasticSearchr能做到: * 在大數據中執行搜索功能時,如果使用MySql,效率太低 * 如果關鍵字輸入不準確,一樣可以搜索到想要的資料 * 將搜索關鍵字,以紅色字體展示 ### ES的介紹 ES是一個使用Java語言並且基於[Lucene](https://lucene.apache.org/)編寫的搜索引擎框架,他提供了分布式的全文搜索功能,提供了一個統一的基於RESTful風格的WebApi接口,官方客戶也對多種語言都提供了相應的API Lucene:本身就是一個搜索引擎的底層。 Lucene官方網站也不推薦直接使用Lucene,再它官方網站中有提供一個框架叫做Solr。 Solr也是基於Lucene去開發的一個框架,因此很多人會拿Solr跟ES做一個對比。 分布式:ES主要是為了突出它的橫向擴展能力(搭建集群)。 全文檢索:將一段詞語進行分詞,並且將分出的單個詞語統一的放到一個分詞庫中,在搜索時,根據關鍵字去分詞庫中檢索,找到匹配的內容。(倒排索引) ![](https://i.imgur.com/86twhX2.png) 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標示去存放數據的位置拉取到指定的數據。 ![](https://img2020.cnblogs.com/blog/2139373/202009/2139373-20200904101427847-1957525614.png) ## 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` 啟動 ![](https://i.imgur.com/W9BaltT.png) 先確認ES有啟動成功,如果有看到下列的JSON格式化面就表示是沒有問題的 ![](https://i.imgur.com/1eIVw5M.png) 若ES沒問題再來訪問kibana `http://127.0.0.1:5601/app/kibana` 進入kibana後,可以進入dev tools執行 webapi操作 ![](https://i.imgur.com/y6h33Dn.png) ### 安裝分詞器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 容器編號` ![](https://i.imgur.com/wHZh3AF.png) #### 在線安裝 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 容器編號` ![](https://i.imgur.com/Suk5vfI.png) ![](https://i.imgur.com/kKyrIf3.png) 默認的分詞器 ![](https://i.imgur.com/vv5PK4h.png) 可以測試一下IK分詞器 ![](https://i.imgur.com/4AGO2m2.png) ## ElasticSearch基本操作 ### ES的結構 #### 索引index :::info ES的服務中,可以建立多個索引。 每一個索引默認被分成5片儲存。 每一個分片都會存在至少一個備份分片。 備份分片默認不會幫助搜索資料,當ES搜索壓力特別大的時候,備份分片才會幫助搜索資料。 備份的分片必須放在不同的服務器中。 ::: ![](https://i.imgur.com/sLBldu3.png) 分片: ES在默認情況下一個索引會被分成五個分片 ![](https://i.imgur.com/82iBnRg.png) 備份(一般情況下,從分片不會去分擔搜索壓力) ![](https://i.imgur.com/N9ZcBrg.png) #### 類型Type :::info 一個索引下,可以建立多個類型。 PS:根據版本不同,類型的建立規則也不同。 ::: Type vs Table ![](https://i.imgur.com/N8RB4ep.png) * ES5.X 版本中,一個index下可以建立多個Type * ES6.X 版本中,一個index下可以建議一個Type * ES7.X 版本中,一個index下沒有Type #### 文件Doc :::info 一個類型下,可以有多的文件,這個文件就類似於Sql表中的多行資料。 ::: document=>一筆一筆資料 ![](https://i.imgur.com/hz2KgO3.png) #### 屬性Field ::: info 一個文件中,可以包含多個屬性,類似於SQL表中的一行資料存在多個列。 ::: field =>欄位 ![](https://i.imgur.com/xKtn4VV.png) ### 操作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 //備份數量 } } ``` ![](https://i.imgur.com/ZirvASo.png) #### 查看索引 可以到management > Index Management 查看剛剛建立的索引 正常情況下健康狀況會是綠色,但這裡的健康狀況是黃色因為目前是單機版啟動,所以沒有其他台主機可以做備份 ![](https://i.imgur.com/sKxpKA5.png) ```json= #查看索引訊息 GET /person ``` ![](https://i.imgur.com/M6z3uZM.png) #### 刪除索引 ```json= #刪除索引 DELETE /person ``` ![](https://i.imgur.com/KI7GfbO.png) ### 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" } } } } } ``` ![](https://i.imgur.com/2y5M1E0.png) * 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" } ``` ![](https://i.imgur.com/w1m5WRm.jpg) 這裡可以看出產生的id很詭異 **手動指定id(更推薦)** ```json= # 建立資料、手動指定id PUT /book/novel/1 { "name":"紅樓夢", "authoor":"曹雪晴", "onsale":"1985-01-04" } ``` ![](https://i.imgur.com/uPh29Um.png) #### 修改資料 覆蓋式修改 ```json= # 建立資料、手動指定id PUT /book/novel/1 { "name":"紅樓夢", "authoor":"曹雪晴", "onsale":"1985-01-04" } ``` doc修改方式(更推薦) 只需要指定需要修改的field對應的值 ```json= #doc修改方式(更推薦) POST /book/novel/1/_update { "doc": { "authoor":"曹奶奶" } } ``` ![](https://i.imgur.com/FEbz1Xm.png) 這裡也可以發現版本從1變成了2 #### 刪除資料 ```json= #根據id刪除檔案 DELETE /book/novel/_id ``` ![](https://i.imgur.com/pJ3CBK4.png) ## 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)))); ``` ![](https://i.imgur.com/XkCzvh5.png) 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); ``` ![](https://i.imgur.com/UYxW3TN.png) #### 修改資料以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); ``` ![](https://i.imgur.com/W5RxNZy.png) #### 刪除資料 ```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); ``` ![](https://i.imgur.com/Cav4Qib.png) ## ElasticSearch練習 :::info 索引&類型名稱統一叫做: String index = "sms_logs_index"; String type = "sms_logs_type"; ::: ![](https://i.imgur.com/fFywdMG.png) 建立一個類別,並透過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 ``` ![](https://i.imgur.com/yYnbk1t.png) [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 } } } } ``` ![](https://i.imgur.com/6kHxzQL.png) ```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" } } } } ``` 輸出結果: ![](https://i.imgur.com/qR56Uv9.png) ##### 其他查詢 [官方的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" } } } ``` ![經緯度查詢](https://i.imgur.com/F0jPl0d.png) 以兩個點圍成一個矩形,獲取矩形內的資料 ```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 } ] } } } } ```