# 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`