昨天是感恩節,上幼兒園的女兒在老師的叮囑下,晚上為我和老婆洗了腳(形式上的^_^),還給我們每人端了一杯水。看著孩子一天天的長大,懂事,感覺很開心。話說咱們程式員這麼辛苦是為了什麼?不就是為了老婆,孩子,熱炕頭,有一個溫暖幸福的家庭,再捎帶著用代碼改變一下世界嗎?想到這裡,頓時覺得學習,創作博客的勁... ...
目錄
引言
昨天是感恩節,上幼兒園的女兒在老師的叮囑下,晚上為我和老婆洗了腳(形式上的^_^),還給我們每人端了一杯水。看著孩子一天天的長大,懂事,感覺很開心,話說咱們程式員這麼辛苦是為了什麼?不就是為了老婆,孩子,熱炕頭,有一個溫暖幸福的家庭,再捎帶著用代碼改變一下世界嗎?想到這裡,頓時覺得學習,創作博客的勁頭也的更足了。哈哈,扯遠了,書歸正傳,今天我們來聊聊 Match Query。
Match Query 是最常用的 Full Text Query 。無論需要查詢什麼欄位, match
查詢都應該會是首選的查詢方式。它既能處理全文欄位,又能處理精確欄位。
構建示例
為了能夠在後面能深入理解 Match Query 中的各個屬性的意義,我們先構建一個 index 示例(有興趣的同學只要將下麵欄位粘貼到 sense 中就可以創建)。
PUT matchtest
{
}
PUT matchtest/_mapping/people
{
"properties": {
"age": {
"type": "integer"
},
"hobbies": {
"type": "text"
},
"name": {
"type": "keyword"
}
}
}
PUT matchtest/people/1
{
"name" : "Jim",
"age": 10,
"hobbies": "football, basketball, pingpang"
}
PUT matchtest/people/2
{
"name" : "Tom",
"age": 12,
"hobbies": "swimming, football"
}
match
operator 參數
match
查詢是一種 bool
類型的查詢。什麼意思呢?舉個例子,查詢 people type 的 hobbies 為 football basketball
GET matchtest/people/_search
{
"query": {
"match": {
"hobbies": "football basketball"
}
}
}
會將上面的兩個文檔都搜索出來。為什麼?上面的查詢其實隱藏了一個預設參數operator
, 它的預設值是 or
,也就是說上面的查詢也可以寫成這種形式
GET matchtest/people/_search
{
"query": {
"match": {
"hobbies": {
"query": "football basketball",
"operator": "or"
}
}
}
}
這樣就比較容易理解了,既然是 or
操作符,就表示只要查詢的文檔的 hobbies
欄位中含有 football
和 basketball
任意一個,就可以被匹配到。
如果將 operator
操作符的值改為 and
,則表示需要同時包含 football
和 basketball
, 得到的結果就只能是 文檔 1 Jim 小朋友了。
analyzer
analyzer
屬性是指在對查詢文本分析時的分析器
- 如果沒有指定則會使用欄位mapping 時指定的分析器
- 如果欄位在 mapping 時也沒有明顯指定,則會使用預設的 search analyzer。
這裡我們也沒有指定,就會使用預設的,就不舉例了,在後面文章講解 analyzer 時再拓展。
lenient 參數
預設值是 false
, 表示用來在查詢時如果數據類型不匹配且無法轉換時會報錯。如果設置成 true
會忽略錯誤。
例如, 例子中的 age
是 integer
類型的,如果查詢 age=xxy
,就會導致無法轉換而報錯。
GET matchtest/_search
{
"query": {
"match": {
"age" : {
"query": "xxx"
}
}
}
}
而如果將 lenient
參數設置為 true
,就會忽略這個錯誤
GET matchtest/_search
{
"query": {
"match": {
"age" : {
"query": "xxx",
"lenient": true
}
}
}
}
註意,如果將 age
欄位的值設置為字元串 "10", 來查詢,由於能夠轉換成整數,這時 elastic 內部會將 字元串先轉換成整數再做查詢,不會報錯。
Fuzziness
fuzzniess 參數
fuzziness
參數可以使查詢的欄位具有模糊搜索的特性。來先瞭解下什麼是模糊搜索。
什麼是模糊搜索?
模糊搜索是指系統允許被搜索信息和搜索提問之間存在一定的差異,這種差異就是“模糊”在搜索中的含義。例如,查找名字Smith時,就會找出與之相似的Smithe, Smythe, Smyth, Smitt等。
——百度百科
通過模糊搜索可以查詢出存在一定相似度的單詞,那麼怎麼計算兩個單詞是否有相似度以及相似度的大小呢?這就要瞭解下另外一個概念:Levenshtein Edit Distance
Levenshtein Edit Distance
Levenshtein Edit Distance 叫做萊文斯坦距離**,是編輯距離的一種。指兩個字串之間,由一個轉成另一個所需的最少編輯操作次數。允許的編輯操作包括將一個字元替換成另一個字元,插入一個字元,刪除一個字元。
例如,單詞 "god" 只需要插入一個 'o' 字元就可以變為 "good",因此它們之間的編輯距離為 1。
fuzziness 參數取值規則
瞭解了上面兩個概念,回過頭再來看下 fuzziness
參數。
在查詢 text
或者 keyword
類型的欄位時, fuzziness
可以看做是萊文斯坦距離。
fuzziness
參數的取值如下
0,1,2
表示最大可允許的萊文斯坦距離AUTO
會根據詞項的長度來產生可編輯距離,它還有兩個可選參數,形式為
AUTO:[low],[high]
, 分別表示短距離參數和長距離參數;如果沒有指定,預設值是AUTO:3,6
表示的意義如下0..2
單詞長度為 0 到 2 之間時必須要精確匹配,這其實很好理解,單詞長度太短是沒有相似度可言的,例如 'a' 和 'b'。
3..5
單詞長度 3 到 5 個字母時,最大編輯距離為 1
>5
單詞長度大於 5 個字母時,最大編輯距離為 2
最佳實踐:
fuzziness
在絕大多數場合都應該設置成AUTO
如果不設置 fuziness
參數,查詢是精確匹配的。
來看例子,上面創建了一個 doc
PUT matchtest/people/1
{
"name" : "Jim",
"age": 10,
"hobbies": "football, basketball, pingpang"
}
設置 fuzziness
為 AUTO
,
- 其中
hobbies
欄位的值football
長度 > 5, 此時我們找一個編輯距離為 2 的單詞footba22
來查詢,應該匹配到 - 其中
name
欄位的值jim
長度在 3 和 5 之間,此時找一個編輯距離為 1 的單詞jiO
應匹配到,而編輯距離為 2 的jOO
就不應匹配到。
來,驗證下
GET matchtest/_search
{
"query": {
"match": {
"hobbies": {
"query": "footba22",
"fuzziness": "AUTO"
}
}
}
}
GET matchtest/_search
{
"query": {
"match": {
"name": {
"query": "jiO",
"fuzziness": "AUTO"
}
}
}
}
GET matchtest/_search
{
"query": {
"match": {
"name": {
"query": "jOO",
"fuzziness": "AUTO"
}
}
}
}
prefix_length
prefix_length
表示不能沒模糊化的初始字元數。由於大部分的拼寫錯誤發生在詞的結尾,而不是詞的開始,使用 prefix_length
就可以完成優化。註意 prefix_length
必須結合 fuzziness
參數使用。
例如,在查詢 hobbies
中的 football
時,將 prefix_length
參數設置為 3,這時 foatball
將不能被匹配。
GET matchtest/_search
{
"query": {
"match": {
"hobbies": {
"query": "foatball",
"fuzziness": "AUTO",
"prefix_length": 3
}
}
}
}
TODO(max_expansions 參數對於 match 查詢而言,沒理解表示的意義,如果您知道這個參數的用法,請給我留言告知,不勝感謝! )
Zero terms Query
先看例子, 先創建一個文檔 zero_terms_query_test
其中 message
欄位使用 stop
分析器,這個分析器會將 stop words 停用詞在索引時全都去掉。
PUT matchtest1
PUT matchtest1/_mapping/zero_terms_query_test
{
"properties": {
"message": {
"type": "text",
"analyzer": "stop"
}
}
}
PUT matchtest1/zero_terms_query_test/1
{
"message": "to be or not to be"
}
GET matchtest1/_search
{
"query": {
"match": {
"message": {
"query": "to be or not to be",
"operator": "and",
"zero_terms_query": "none"
}
}
}
}
那麼就像 message 欄位中的 to be or not to be
這個短語中全部都是停止詞,一過濾,就什麼也沒有了,得不到任何 tokens, 那搜索時豈不什麼都搜不到。
POST _analyze
{
"analyzer": "stop",
"text": "to be or not to be"
}
zero_terms_query
就是為瞭解決這個問題而生的。它的預設值是 none
,就是搜不到停止詞(對 stop 分析器欄位而言),如果設置成 all
,它的效果就和 match_all
類似,就可以搜到了。
GET matchtest1/_search
{
"query": {
"match": {
"message": {
"query": "to be or not to be",
"operator": "and",
"zero_terms_query": "all"
}
}
}
}
Cutoff frequency
查詢字元串時的詞項會分成低頻詞(更重要)和高頻詞(次重要)兩類,像前面所說的停用詞 (stop word)就屬於高頻詞,它雖然出現頻率較高,但在匹配時可能並不太相關。實際上,我們往往是想要文檔能儘可能的匹配那些低頻詞,也就是更重要的詞項。
要實現這個需求,只要在查詢時配置 cutoff_frequency
參數就可以了。假設我們將 cutoff_frequency
設置成 0.01
就表示
- 任何詞項在文檔中超過 1%, 被認為是高頻詞
- 其他的詞項會被認為低頻詞
從而將高頻詞(次重要的詞)挪到可選子查詢中,讓它們只參與評分,而不參與匹配;高頻詞(更重要的詞)參與匹配和評分。
這樣一來,就不再需要 stopwords 停用詞文件了,從而變成了動態生成停用詞: 高頻詞就會被看做是停用詞。這種配置只是對於詞項比較多的場合如 email body,文章等適用,文字太少, cutoff_frequency
選項設置的意義就不大了。
cutoff_frequency
配置有兩種形式
- 指定為一個分數(
0.01
)表示出現頻率 - 指定為一個正整數(
5
)則表示出現次數
下麵給個例子, 在創建的 3 個文檔中都包含 "be " 的單詞,在查詢時將 cutoff_frequency
參數設置為 2, 表示 "be" 就是高頻詞,只會參與評分,但在匹配時不做考慮。
此時查詢的內容為 "to be key" ,由於 "be" 詞項是高頻詞,因為在文檔中必須要存在 "to" 或者 "key" 才能匹配,因此文檔 3 不能匹配。
PUT /matchtest2
PUT matchtest2/_mapping/cutoff_frequency_test
{
"properties": {
"message": {
"type": "text"
}
}
}
PUT matchtest2/cutoff_frequency_test/1
{
"message": "to be or not to be to be or"
}
PUT matchtest2/cutoff_frequency_test/2
{
"message": "be key or abc"
}
PUT matchtest2/cutoff_frequency_test/3
{
"message": "or to be or to to be or"
}
GET matchtest2/_search
{
"query": {
"match": {
"message": {
"query": "to be key",
"cutoff_frequency": 2
}
}
}
}
synonyms
synonyms 是指同義詞,只要索引和欄位中配置了同義詞過濾器,match 查詢是支持多詞條的同義詞擴展的。在應用過濾器後,解析器會對每個多次條同義詞創建一個語句查詢。
例如,同義詞 USA, united states of America
就會構建出 (USA OR ("united states of America"))
。看下麵例子:
PUT /matchtest4
{
"settings": {
"index" : {
"analysis" : {
"analyzer" : {
"synonym" : {
"tokenizer" : "whitespace",
"filter" : ["synonym"]
}
},
"filter" : {
"synonym" : {
"type" : "synonym",
"synonyms" : [
"USA, united states of America"
]
}
}
}
}
}
}
PUT /matchtest4/_mapping/synonyms_test
{
"properties": {
"message": {
"type": "text",
"analyzer": "synonym"
}
}
}
PUT /matchtest4/synonyms_test/1
{
"message": "united states of America people"
}
GET /matchtest4/_search
{
"query": {
"match": {
"message": {
"query": "USA"
}
}
}
}
小結
本文以代碼實例的方式完整的講解了 Match Query 的各種使用場景和參數意義。下篇會講解 Match Phrase Query 敬請期待。
參考文檔
系列文章列表
Query DSL
Java Rest Client API
- Elasticsearch Java Rest Client API 整理總結 (一)——Document API
- Elasticsearch Java Rest Client API 整理總結 (二) —— SearchAPI
- Elasticsearch Java Rest Client API 整理總結 (三)——Building Queries