Elasticsearch Query DSL 整理總結(二)—— 要搞懂 Match Query,看這篇就夠了


引言

昨天是感恩節,上幼兒園的女兒在老師的叮囑下,晚上為我和老婆洗了腳(形式上的_),還給我們每人端了一杯水。看着孩子一天天的長大,懂事,感覺很開心,話說咱們程序員這么辛苦是為了什么?不就是為了老婆,孩子,熱炕頭,有一個溫暖幸福的家庭,再捎帶着用代碼改變一下世界嗎?想到這里,頓時覺得學習,創作博客的勁頭也的更足了。哈哈,扯遠了,書歸正傳,今天我們來聊聊 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 字段中含有 footballbasketball 任意一個,就可以被匹配到。

如果將 operator 操作符的值改為 and ,則表示需要同時包含 footballbasketball , 得到的結果就只能是 文檔 1 Jim 小朋友了。

analyzer

analyzer 屬性是指在對查詢文本分析時的分析器

  • 如果沒有指定則會使用字段mapping 時指定的分析器
  • 如果字段在 mapping 時也沒有明顯指定,則會使用默認的 search analyzer。

這里我們也沒有指定,就會使用默認的,就不舉例了,在后面文章講解 analyzer 時再拓展。

lenient 參數

默認值是 false , 表示用來在查詢時如果數據類型不匹配且無法轉換時會報錯。如果設置成 true 會忽略錯誤。

例如, 例子中的 ageinteger 類型的,如果查詢 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"
}

設置 fuzzinessAUTO

  • 其中 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

  1. Query DSL 概要,MatchAllQuery,全文查詢簡述

Java Rest Client API

  1. Elasticsearch Java Rest Client API 整理總結 (一)——Document API
  2. Elasticsearch Java Rest Client API 整理總結 (二) —— SearchAPI
  3. Elasticsearch Java Rest Client API 整理總結 (三)——Building Queries


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM