# Elasticsearch (三) - 查詢和過濾 Elasticsearch 的搜尋提供了許多的語法來進行查詢,透過組合這些語法可以查詢出各式各樣的結果。 <!-- more --> ## 查詢 (Query) 和過濾 (Filter) DSL 這個查詢組件可以以無限組合的方式進行搭配,而他可以使用在查詢和過濾兩種情況。 當使用過濾情況時,只會針對 Document 是否匹配回傳 Yes、No。例如 : * created 是否介於 2019 和 2020 之間。 當使用查詢情況時,除了會看 Document 是否匹配,還會計算得分,也就是判斷這個 Document 有多匹配。例如 : * run 這個詞也能匹配 runs、running、jog、sprint。 * 標籤重複可能相關性越高 查詢情況這種擁有評分機制的作法非常適合全文檢索,因為全文檢索沒有完全正確的答案。 **效能差異** 過濾查詢 (Filtering queries) 只是簡單的檢查是否包含,這樣計算的方式就非常快速,因此又稱為不評分查詢。此外,考量到過濾查詢有些 Document 很少匹配到,一旦匹配成功 Document 就會被存進 Memory 以利被快速讀去。 評分查詢 (Scoring queries) 除了要找出匹配的 Document 外,還需要計算每個 Document 的相關性來做評分,因此相比過濾查詢不須進行評分來說效能勢必較差。此外,評分查詢的結果也不會存到 Memory,這又讓他的速度更慢了。 不過多虧於反向索引,一個簡單的評分查詢在匹配少量的 Document 時可能跟過濾查詢要找上百萬個 Document 的表現一樣好。但是相比下還是過濾效能會較優異,而過濾查詢的目標就是要減少那些需要評分查詢進行檢查的 Document。 **選擇過濾和查詢的時機** 查詢一般用於進行全文檢索或者其他需要影響相關性得分的搜尋,除此之外其他都用過濾。 ## 查詢 (Query) Elasticsearch 有許多查詢的方式,而查詢方式只要將查詢的語句傳給 `query` 參數即可,如下 : ```json= GET /_search { "query" : <Query_Here> } ``` **範例** 查詢所有 Document,`{"match_all":{}}` 就是查詢的條件。 ```json= GET /_search { "query":{ "match_all":{} } } ``` 以下介紹一些常用的查詢語法。 ### match_all match_all 會匹配出所有的 Document。 ```json= GET <Index>/<Type>/_search { "query":{ "match_all":{} } } ``` **範例** ```json= GET sport/basketball/_search { "query":{ "match_all":{} } } ``` 查詢的結果如下,首先可以看到這一段是指請求耗時多久以及是否超時。耗時的單位是毫秒。 ```json= "took" : 2, "timed_out" : false, ``` 這一層是這次查詢總共查詢了多少分片。 ```json= "_shards" : { "total" : 5, "successful" : 5, "skipped" : 0, "failed" : 0 }, ``` 最後 hits 這段表示查詢匹配的結果,外層顯示了總共成功匹配的 Document 數量和匹配最高分是多少。內層會顯示出匹配成功的 Document 的資訊。 ```json= "hits" : { "total" : 3, "max_score" : 1.0, "hits" : [ { "_index" : "sport", "_type" : "basketball", "_id" : "2", "_score" : 1.0, "_source" : { "team" : "Lakers", "location" : "Los Angelas", "assests" : null, "champion" : 16, "assets" : 150 } }, { "_index" : "sport", "_type" : "basketball", "_id" : "1", "_score" : 1.0, "_source" : { "team" : "Celtics", "location" : "Boston", "assests" : null, "champion" : 17, "assets" : 100 } }, { "_index" : "sport", "_type" : "basketball", "_id" : "3", "_score" : 1.0, "_source" : { "team" : "Bulls", "location" : "Chicago", "assests" : null, "champion" : 6, "assets" : 180 } } ] } ``` ### match match 可以用於全文檢索,包含句子、單詞、數字和日期都可以處理。如果是句子的話,查詢前 Elasticsearch 會先用分析器 (Analyzer) 進行分析,並把句子拆開來再做查詢。而其他的單詞、數字和日期因為都是一個精確的值,所以在匹配 Document 上就會精確的去查詢,也就是要完全相同。 ```json= GET <Index>/<Type>/_search { "query":{ "match":{ "<Field>": "<Value1> <Value2> ..." } } } ``` **範例** ```json= GET sport/basketball/_search { "query":{ "match":{ "location": "Los Boston Bulls" } } } ``` 下面的結果可以看到 location 有 Los 和 Boston 的都被查詢出來了,而 Bulls 因為在 location 中找不到所以沒有結果。 ```json= "hits" : { "total" : 2, "max_score" : 0.2876821, "hits" : [ { "_index" : "sport", "_type" : "basketball", "_id" : "2", "_score" : 0.2876821, "_source" : { "team" : "Lakers", "location" : "Los Angelas", "assets" : 150, "champion" : 16 } }, { "_index" : "sport", "_type" : "basketball", "_id" : "1", "_score" : 0.2876821, "_source" : { "team" : "Celtics", "location" : "Boston", "assets" : 100, "champion" : 17 } } ] } ``` 除了直接帶值做查詢,Field 還有一些參數可以指定來做到更進階的查詢。例如 : ```json= GET sport/basketball/_search { "query":{ "match":{ "location": { "query": "Los Angelas Boston Bulls", "minimum_should_match": 2 } } } } ``` ```json= GET sport/basketball/_search { "query":{ "match":{ "location": { "query": "Los Angelas", "operator": "and" } } } } ``` 上面這兩個範例的 Field 加入了一些參數 * query : 原本要查詢的內容 * operator : 可以帶 `and` 或 `or`,預設沒指定是用 or。用 or 的話就是所有的單詞只要其中一個有符合就有可能會被匹配出來。相反的,and 就是全部都一定要有。 * minimum_should_match : 設定查詢內容至少要有幾個符合。更常用的是輸入一個百分比,因為無法預測使用者會輸入幾個單詞,例如,`"75%"`。 更多參數請參考 [Elasticsearch 官網](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-match-query.html)。 ### multi_match multi_match 允許對多個 Field 進行查詢匹配。內層的 query 後面接的是查詢的條件,fields 可以填入所有要搜尋的 Field,這裡可以使用 `*` 作為萬用字元。 ```json= GET <Index>/<Type>/_search { "query":{ "multi_match":{ "query": "<Query>", "fields": ["<Field>", "<Field>", ...] } } } ``` **範例** ```json= GET sport/basketball/_search { "query":{ "multi_match":{ "query": "Los", "fields": ["team", "location"] } } } ``` ### multi_phrase multi_phrase 可以指定以片語來搜尋。片語必須要完全符合,也就是不會被拆開成單詞。 ```json= GET <Index>/<Type>/_search { "query":{ "match_phrase":{ "<Field>": "<Phrase>" } } } ``` **範例** 執行範例之前,我們先新增一個欄位。 ```json= POST sport/basketball/_bulk { "update" : { "_id": 1 } } { "doc":{ "Schedule": "Next game at Los Angelas"}} { "update" : { "_id": 2 } } { "doc":{ "Schedule": "Next game at Home"}} { "update" : { "_id": 3 } } { "doc":{ "Schedule": "Last game at Los Angelas"}} ``` 接下來我們要查詢 Schedule 這個欄位有 Next game 的 Document。 ```json= GET sport/basketball/_search { "query":{ "match_phrase":{ "Schedule": "Next game" } } } ``` 查詢的結果可以看到只有兩個 Document,如果把 match_phrase 換成 match 你會發現結果有三個 Document。這就是因為 mathc_phrase 不會把字段拆開,而 match 會拆開,就會因為都有 `game` 所以三個 Docuemnt 都被查詢出來。 ### multi_phrase_prefix multi_phrase_prefix 和 match_phrase 一樣可以用片語查詢,差別在於只需要輸入前面一部份就可以找出所有相關的 Document。最常見的就是搜尋推薦,只輸入前面幾個字便會跳出很多個選項可以選。 ```json= GET <Index>/<Type>/_search { "query":{ "match_phrase_prefix":{ "<Field>": "<Phrase Prefix>" } } } ``` **範例** 下面這個例子輸入了 `at Lo`,以輸入的資料來說自然就會找到 `at Los Angelas`。如果是一般的情況來說,結果可能就會有 at London、at Loveland 等等。 ```json= GET sport/basketball/_search { "query":{ "match_phrase_prefix":{ "Schedule": "at Lo" } } } ``` ### prefix prefix 用於查詢前綴詞符合輸入的結果,但是只能輸入一個單詞的一部份,不能像 `multi_phrase_prefix` 輸入一段片語的一部份。 ```json= GET <Index>/<Type>/_search { "query":{ "match_phrase_prefix":{ "<Field>": "<Prefix>" } } } ``` **範例** ```json= GET sport/basketball/_search { "query":{ "prefix":{ "Schedule": "lo" } } } ``` ### wildcard wildcard 用於通用查詢,也就是可以使用萬用字元 `*` 和 `?` 來指定通用的查詢條件。`*` 可以代表 0 個或無限多個字元,`?` 代表一個字元。 ```json= GET <Index>/<Type>/_search { "query":{ "match_phrase_prefix":{ "<Field>": "<Prefix>" } } } ``` **範例** 這個範例查詢的結果可能是 game、videogame、gamy 等等。 ```json= GET sport/basketball/_search { "query":{ "wildcard":{ "Schedule": "*gam?" } } } ``` ### term term 用於精確查詢,也就是要完全相同。精確值可以是數字、時間、Boolean 等等。 ```json= GET <Index>/<Type>/_search { "query":{ "term":{ "<Field>": <Value> } } } ``` **範例** ```json= GET sport/basketball/_search { "query":{ "term":{ "assets": 150 } } } ``` ### terms terms 和 term 一樣用於精確查詢,但是 terms 可以查詢多個值,等同於 SQL 的 In。 ```json= GET <Index>/<Type>/_search { "query":{ "terms":{ "<Field>": [<Value>, <Value>, ...] } } } ``` **範例** ```json= GET sport/basketball/_search { "query":{ "terms":{ "assets": [150, 180] } } } ``` ### fuzzy fuzzy 用於模糊查詢,可以解決拼寫錯誤的問題,但是具有很高的 CPU 消耗和很低的精確度。 fuzzy 是以編輯距離來做測量,編輯距離是指更改字元、刪除字元、插入字元、轉置相鄰字元等等的次數。在指定的編輯距離內建立所有可能的詞,再進行匹配。 ```json= GET <Index>/<Type>/_search { "query":{ "fuzzy":{ "<Field>": <Value> } } } ``` **範例** 執行範例前,我們先新增一個欄位,可以看到三個句子裡針對 win 各用了不同的時態。 ```json= POST sport/basketball/_bulk { "update" : { "_id": 1 } } { "doc":{ "News": "Celtics won at New York last night."}} { "update" : { "_id": 2 } } { "doc":{ "News": "Lakers will win at home tomorrow."}} { "update" : { "_id": 3 } } { "doc":{ "News": "Winning the game in the first half."}} ``` 接著進行模糊查詢,這裡刻意把 win 輸成 wen,最後查詢的結果還是會有 Document 被找出來。可以自己試試填入其他的值看看結果會如何。 ```json= GET sport/basketball/_search { "query":{ "fuzzy":{ "News": "wen" } } } ``` ### bool query bool query 用於將多個條件組合在一起,而他主要由三個部份組成 : * must : 所有條件都必須完全匹配,等於 `AND`。 * should : 至少一個條件要匹配,等於 `OR`。 * must_not : 所有條件都不能匹配,等於 `NOT`。 ```json= GET <Index>/<Type>/_search { "query": { "bool": { "must": { "<action>": { "<Field>": <Value> } }, "should": [ { "<action>": { "<Field>": <Value> } }, { "<action>": { "<Field>": <Value> } } ], "must_not": { "<action>": { "<Field>": <Value> } } } } } ``` **範例** ```json= GET sport/basketball/_search { "query": { "bool": { "must": { "match": { "location":"boston" } }, "should": [ { "term": { "assets": 100 } }, { "term": { "champion": 10 } } ], "must_not": { "term": { "Schedule": "no game" } } } } } ``` 上面這個範例轉換成 SQL 的條件如下 : ```sql= SELECT * FROM sport.basketball WHERE location LIKE '%boston%' AND (assets = 100 OR champion = 10) AND Schedule != 'no game' ``` `should` 也可以和 match 一樣設定 `minimum_should_match`。 ### range range 用於查詢落在指定區間的數字或時間。可以使用以下參數來設定區間 : * gt : 大於 * gte : 大於等於 * lt : 小於 * lte : 小於等於 ```json= GET <Index>/<Type>/_search { "query":{ "range":{ "<Field>": { "<gt|gte>": <Value>, "<lt|lte>": <Value> } } } } ``` **範例** ```json= GET sport/basketball/_search { "query":{ "range":{ "champion": { "gt": "10", "lte": "20" } } } } ``` ### exists exists 用於查詢是否有值,類似於 SQL 的 `IS NOT NULL`。 ```json= GET <Index>/<Type>/_search { "query":{ "exists":{ "field": <Field> } } } ``` **範例** ```json= GET sport/basketball/_search { "query":{ "exists":{ "field": "champion" } } } ``` 這裡要注意的是在 field 後面才是接 Field 的名稱,例如上面這個範例,champion 是 Field 的名稱,不是 field 換成名稱。 ## 過濾 (Filter) 在 Elasticsearch 5 以後,Filtered 的過濾用法已經被棄用了,本篇介紹的是 filter 新的用法。 ### bool query 最常見的是結合 bool query 使用,用法就和 must、should、must_not 一樣指定條件。而 filter 的條件如果和 must 一樣,兩個的結果會是一樣的,只差在 filter 不會評分。 ```json= GET <Index>/<Type>/_search { "query": { "bool": { "filter": { "<action>": { "<Field>": <Value> } } } } } ``` **範例** ```json= GET sport/basketball/_search { "query": { "bool": { "must": { "match": { "location": "Boston Los Angelas" } }, "filter": { "term": { "champion": 17 } } } } } ``` 上面這個範例可以看到使用了 must 和 filter,filter 用在不需要評分的 Field,而需要評分的就用其他三個查詢。此外,filter 不會影響到其他三個查詢的評分結果。 ## 其他搭配參數 ### from from 可以設定要從原本查詢出來的結果的第幾筆開始作為結果。from 的值從 0 開始。 ```json= GET <Index>/<Type>/_search { "query":{ "match_all": {} }, "from": <Value> } ``` **範例** ```json= GET sport/basketball/_search { "query":{ "match_all": {} }, "from": 2 } ``` ### size size 可以指定要查詢幾筆。 ```json= GET <Index>/<Type>/_search { "query":{ "match_all": {} }, "size": <Value> } ``` **範例** ```json= GET sport/basketball/_search { "query":{ "match_all": {} }, "size": 2 } ``` 此外 size 也常常跟 from 一起搭配使用。例如 : ```json= GET sport/basketball/_search { "query":{ "match_all": {} }, "from": 0, "size": 2 } ``` ### sort sort 用於依照 Field 來排序,等同於 SQL 的 Order By。可以選擇 asc 或 desc。 ```json= GET <Index>/<Type>/_search { "query":{ "match_all": {} }, "sort": [ { "<Field>": { "order": "<asc|desc>" } } ] } ``` **範例** ```json= GET sport/basketball/_search { "query":{ "match_all": {} }, "sort": [ { "champion": { "order": "asc" } } ] } ``` ### _source _source 可以指定要回傳的 Field, ```json= GET <Index>/<Type>/_search { "query":{ "match_all": {} }, "_source": ["<Field>", "<Field>", ...] } ``` **範例** ```json= GET sport/basketball/_search { "query":{ "match_all": {} }, "_source": ["team", "champion"] } ``` ## Summary 本篇介紹了介紹了如何查詢 Document。這些語法可以任意的組合出各種情況,要稍微適應 Elasticsearch 和關聯式資料庫的不同會需要一點時間。 [下一篇](https://tienyulin.github.io/elasticsearch-query-sql-xpack) 將介紹除了用 DSL 進行查詢,Elasticsearch 還提供了以 SQL 語法來做查詢的功能。 ## 參考 [1] [ES系列之利用filter讓你的查詢效率飛起来](https://blog.csdn.net/pony_maggie/article/details/106062284?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-5.channel_param&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-5.channel_param) [2] [要搞懂 Elasticsearch Match Query,看這篇就夠了](https://segmentfault.com/a/1190000017110948) [3] [elasticsearch 查詢(match和term)](https://www.cnblogs.com/yjf512/p/4897294.html) [4] [Elasticsearch: 權威指南](https://www.elastic.co/guide/cn/elasticsearch/guide/current/index.html) [5] [Elasticsearch CodingDict](http://doc.codingdict.com/elasticsearch/) [6] [ElasticSearch從入門到精通](https://www.itread01.com/content/1546464245.html) [7] [Elasticsearch:fuzzy 搜索 (模糊搜索)](https://blog.csdn.net/UbuntuTouch/article/details/101287399) [8] [Search API | 台部落](https://www.twblogs.net/a/5d6676f8bd9eee5327feae8a) [9] [ElasticSearch - term 和 match 的差別](https://kucw.github.io/blog/2018/6/elasticsearch-term-match/) ###### tags: `Elasticsearch` `NoSQL`