0%

Elasticsearch (三) - 查詢和過濾

Elasticsearch 的搜尋提供了許多的語法來進行查詢,透過組合這些語法可以查詢出各式各樣的結果。

查詢 (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 參數即可,如下 :

1
2
3
4
GET /_search
{
"query" : <Query_Here>
}

範例
查詢所有 Document,{"match_all":{}} 就是查詢的條件。

1
2
3
4
5
6
GET /_search
{
"query":{
"match_all":{}
}
}

以下介紹一些常用的查詢語法。

match_all

match_all 會匹配出所有的 Document。

1
2
3
4
5
6
GET <Index>/<Type>/_search
{
"query":{
"match_all":{}
}
}

範例

1
2
3
4
5
6
GET sport/basketball/_search
{
"query":{
"match_all":{}
}
}

查詢的結果如下,首先可以看到這一段是指請求耗時多久以及是否超時。耗時的單位是毫秒。

1
2
"took" : 2,
"timed_out" : false,

這一層是這次查詢總共查詢了多少分片。

1
2
3
4
5
6
"_shards" : {
"total" : 5,
"successful" : 5,
"skipped" : 0,
"failed" : 0
},

最後 hits 這段表示查詢匹配的結果,外層顯示了總共成功匹配的 Document 數量和匹配最高分是多少。內層會顯示出匹配成功的 Document 的資訊。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
"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 上就會精確的去查詢,也就是要完全相同。

1
2
3
4
5
6
7
8
GET <Index>/<Type>/_search
{
"query":{
"match":{
"<Field>": "<Value1> <Value2> ..."
}
}
}

範例

1
2
3
4
5
6
7
8
GET sport/basketball/_search
{
"query":{
"match":{
"location": "Los Boston Bulls"
}
}
}

下面的結果可以看到 location 有 Los 和 Boston 的都被查詢出來了,而 Bulls 因為在 location 中找不到所以沒有結果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
"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 還有一些參數可以指定來做到更進階的查詢。例如 :

1
2
3
4
5
6
7
8
9
10
11
GET sport/basketball/_search
{
"query":{
"match":{
"location": {
"query": "Los Angelas Boston Bulls",
"minimum_should_match": 2
}
}
}
}
1
2
3
4
5
6
7
8
9
10
11
GET sport/basketball/_search
{
"query":{
"match":{
"location": {
"query": "Los Angelas",
"operator": "and"
}
}
}
}

上面這兩個範例的 Field 加入了一些參數

  • query : 原本要查詢的內容
  • operator : 可以帶 andor,預設沒指定是用 or。用 or 的話就是所有的單詞只要其中一個有符合就有可能會被匹配出來。相反的,and 就是全部都一定要有。
  • minimum_should_match : 設定查詢內容至少要有幾個符合。更常用的是輸入一個百分比,因為無法預測使用者會輸入幾個單詞,例如,"75%"

更多參數請參考 Elasticsearch 官網

multi_match

multi_match 允許對多個 Field 進行查詢匹配。內層的 query 後面接的是查詢的條件,fields 可以填入所有要搜尋的 Field,這裡可以使用 * 作為萬用字元。

1
2
3
4
5
6
7
8
9
GET <Index>/<Type>/_search
{
"query":{
"multi_match":{
"query": "<Query>",
"fields": ["<Field>", "<Field>", ...]
}
}
}

範例

1
2
3
4
5
6
7
8
9
GET sport/basketball/_search
{
"query":{
"multi_match":{
"query": "Los",
"fields": ["team", "location"]
}
}
}

multi_phrase

multi_phrase 可以指定以片語來搜尋。片語必須要完全符合,也就是不會被拆開成單詞。

1
2
3
4
5
6
7
8
GET <Index>/<Type>/_search
{
"query":{
"match_phrase":{
"<Field>": "<Phrase>"
}
}
}

範例
執行範例之前,我們先新增一個欄位。

1
2
3
4
5
6
7
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。

1
2
3
4
5
6
7
8
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。最常見的就是搜尋推薦,只輸入前面幾個字便會跳出很多個選項可以選。

1
2
3
4
5
6
7
8
GET <Index>/<Type>/_search
{
"query":{
"match_phrase_prefix":{
"<Field>": "<Phrase Prefix>"
}
}
}

範例
下面這個例子輸入了 at Lo,以輸入的資料來說自然就會找到 at Los Angelas。如果是一般的情況來說,結果可能就會有 at London、at Loveland 等等。

1
2
3
4
5
6
7
8
GET sport/basketball/_search
{
"query":{
"match_phrase_prefix":{
"Schedule": "at Lo"
}
}
}

prefix

prefix 用於查詢前綴詞符合輸入的結果,但是只能輸入一個單詞的一部份,不能像 multi_phrase_prefix 輸入一段片語的一部份。

1
2
3
4
5
6
7
8
GET <Index>/<Type>/_search
{
"query":{
"match_phrase_prefix":{
"<Field>": "<Prefix>"
}
}
}

範例

1
2
3
4
5
6
7
8
GET sport/basketball/_search
{
"query":{
"prefix":{
"Schedule": "lo"
}
}
}

wildcard

wildcard 用於通用查詢,也就是可以使用萬用字元 *? 來指定通用的查詢條件。* 可以代表 0 個或無限多個字元,? 代表一個字元。

1
2
3
4
5
6
7
8
GET <Index>/<Type>/_search
{
"query":{
"match_phrase_prefix":{
"<Field>": "<Prefix>"
}
}
}

範例
這個範例查詢的結果可能是 game、videogame、gamy 等等。

1
2
3
4
5
6
7
8
GET sport/basketball/_search
{
"query":{
"wildcard":{
"Schedule": "*gam?"
}
}
}

term

term 用於精確查詢,也就是要完全相同。精確值可以是數字、時間、Boolean 等等。

1
2
3
4
5
6
7
8
GET <Index>/<Type>/_search
{
"query":{
"term":{
"<Field>": <Value>
}
}
}

範例

1
2
3
4
5
6
7
8
GET sport/basketball/_search
{
"query":{
"term":{
"assets": 150
}
}
}

terms

terms 和 term 一樣用於精確查詢,但是 terms 可以查詢多個值,等同於 SQL 的 In。

1
2
3
4
5
6
7
8
GET <Index>/<Type>/_search
{
"query":{
"terms":{
"<Field>": [<Value>, <Value>, ...]
}
}
}

範例

1
2
3
4
5
6
7
8
GET sport/basketball/_search
{
"query":{
"terms":{
"assets": [150, 180]
}
}
}

fuzzy

fuzzy 用於模糊查詢,可以解決拼寫錯誤的問題,但是具有很高的 CPU 消耗和很低的精確度。

fuzzy 是以編輯距離來做測量,編輯距離是指更改字元、刪除字元、插入字元、轉置相鄰字元等等的次數。在指定的編輯距離內建立所有可能的詞,再進行匹配。

1
2
3
4
5
6
7
8
GET <Index>/<Type>/_search
{
"query":{
"fuzzy":{
"<Field>": <Value>
}
}
}

範例
執行範例前,我們先新增一個欄位,可以看到三個句子裡針對 win 各用了不同的時態。

1
2
3
4
5
6
7
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 被找出來。可以自己試試填入其他的值看看結果會如何。

1
2
3
4
5
6
7
8
GET sport/basketball/_search
{
"query":{
"fuzzy":{
"News": "wen"
}
}
}

bool query

bool query 用於將多個條件組合在一起,而他主要由三個部份組成 :

  • must : 所有條件都必須完全匹配,等於 AND
  • should : 至少一個條件要匹配,等於 OR
  • must_not : 所有條件都不能匹配,等於 NOT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
GET <Index>/<Type>/_search
{
"query": {
"bool": {
"must": {
"<action>": {
"<Field>": <Value>
}
},
"should": [
{
"<action>": {
"<Field>": <Value>
}
},
{
"<action>": {
"<Field>": <Value>
}
}
],
"must_not": {
"<action>": {
"<Field>": <Value>
}
}
}
}
}

範例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
GET sport/basketball/_search
{
"query": {
"bool": {
"must": {
"match": {
"location":"boston"
}
},
"should": [
{
"term": {
"assets": 100
}
},
{
"term": {
"champion": 10
}
}
],
"must_not": {
"term": {
"Schedule": "no game"
}
}
}
}
}

上面這個範例轉換成 SQL 的條件如下 :

1
2
3
4
5
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 : 小於等於
1
2
3
4
5
6
7
8
9
10
11
GET <Index>/<Type>/_search
{
"query":{
"range":{
"<Field>": {
"<gt|gte>": <Value>,
"<lt|lte>": <Value>
}
}
}
}

範例

1
2
3
4
5
6
7
8
9
10
11
GET sport/basketball/_search
{
"query":{
"range":{
"champion": {
"gt": "10",
"lte": "20"
}
}
}
}

exists

exists 用於查詢是否有值,類似於 SQL 的 IS NOT NULL

1
2
3
4
5
6
7
8
GET <Index>/<Type>/_search
{
"query":{
"exists":{
"field": <Field>
}
}
}

範例

1
2
3
4
5
6
7
8
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 不會評分。

1
2
3
4
5
6
7
8
9
10
11
12
GET <Index>/<Type>/_search
{
"query": {
"bool": {
"filter": {
"<action>": {
"<Field>": <Value>
}
}
}
}
}

範例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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 開始。

1
2
3
4
5
6
7
GET <Index>/<Type>/_search
{
"query":{
"match_all": {}
},
"from": <Value>
}

範例

1
2
3
4
5
6
7
GET sport/basketball/_search
{
"query":{
"match_all": {}
},
"from": 2
}

size

size 可以指定要查詢幾筆。

1
2
3
4
5
6
7
GET <Index>/<Type>/_search
{
"query":{
"match_all": {}
},
"size": <Value>
}

範例

1
2
3
4
5
6
7
GET sport/basketball/_search
{
"query":{
"match_all": {}
},
"size": 2
}

此外 size 也常常跟 from 一起搭配使用。例如 :

1
2
3
4
5
6
7
8
GET sport/basketball/_search
{
"query":{
"match_all": {}
},
"from": 0,
"size": 2
}

sort

sort 用於依照 Field 來排序,等同於 SQL 的 Order By。可以選擇 asc 或 desc。

1
2
3
4
5
6
7
8
9
10
11
12
13
GET <Index>/<Type>/_search
{
"query":{
"match_all": {}
},
"sort": [
{
"<Field>": {
"order": "<asc|desc>"
}
}
]
}

範例

1
2
3
4
5
6
7
8
9
10
11
12
13
GET sport/basketball/_search
{
"query":{
"match_all": {}
},
"sort": [
{
"champion": {
"order": "asc"
}
}
]
}

_source

_source 可以指定要回傳的 Field,

1
2
3
4
5
6
7
GET <Index>/<Type>/_search
{
"query":{
"match_all": {}
},
"_source": ["<Field>", "<Field>", ...]
}

範例

1
2
3
4
5
6
7
GET sport/basketball/_search
{
"query":{
"match_all": {}
},
"_source": ["team", "champion"]
}

Summary

本篇介紹了介紹了如何查詢 Document。這些語法可以任意的組合出各種情況,要稍微適應 Elasticsearch 和關聯式資料庫的不同會需要一點時間。

下一篇 將介紹除了用 DSL 進行查詢,Elasticsearch 還提供了以 SQL 語法來做查詢的功能。

參考

[1] ES系列之利用filter讓你的查詢效率飛起来
[2] 要搞懂 Elasticsearch Match Query,看這篇就夠了
[3] elasticsearch 查詢(match和term)
[4] Elasticsearch: 權威指南
[5] Elasticsearch CodingDict
[6] ElasticSearch從入門到精通
[7] Elasticsearch:fuzzy 搜索 (模糊搜索)
[8] Search API | 台部落
[9] ElasticSearch - term 和 match 的差別