對於剛接觸搜索或者Elasticsearch的小白來說對queryString可能接觸的不多,但是對於早期從事搜索的人來說queryString並不會陌生,它可以理解成檢索表達式,但並不是elasticsearch的queryDSL,他遵從的是Lucene語法。elasticsearch同樣有接口應用於queryString。下面上個例子:
{
"from": 0,
"size": 100,
"query": {
"query_string": {
"query": "TITLE:無人機"
}
}
}
“TITLE:無人機”就是一個queryString,是一個字符串,表示在TITLE字段中查詢匹配“無人機”。
它還支持一些參數:
{
"from": 0,
"size": 100,
"query": {
"query_string": {
"query": "TITLE:無人機"
"fields": [],
"type": "best_fields",
"default_operator": "or",
"max_determinized_states": 10000,
"enable_position_increments": true,
"fuzzy_transpositions": true,
"boost": 1
}
}
}
具體的關於queryString本篇不再過多介紹,這裏不是重點。
參考官網:https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-query-string-query.html
從上述例子中我們知道了我們的需求是從TITLE中匹配“無人機”這個詞。這個檢索式沒任何問題,因爲一般的分詞器都會把無人機這個詞分出來,但隨着我們檢索的深入,我們會遇到一個問題,比如我們要搜索“鋁合金輪轂”這個詞,一般的分詞器都不會分出這個詞來,也就是我們所說的專有名詞詞庫。那麼會出現什麼問題呢?我們試試就知道了
{
"query": {
"query_string": {
"query": "TITLE:鋁合金輪轂"
}
}
}
我們會發現匹配結果包含很多鋁合金和輪轂的文檔,可想而知是因爲在檢索的時候,是先把“鋁合金輪轂‘這個詞先分詞,再去檢索的,類似match檢索。那麼這樣的話就會帶來兩個問題:
- 如果我不想用match,我想精準匹配只返回帶“鋁合金輪轂”而不是“鋁合金”+“輪轂”怎麼辦?
- 如果詞庫裏沒有“鋁合金輪轂”這個詞,該怎麼做精準匹配?
說到這,可能有些人就會說了,你把“鋁合金輪轂”這個詞加上引號不就行了。我們來具體看看行還是不行:
{
"query": {
"query_string": {
"query": "TITLE:\"鋁合金輪轂\""
}
}
}
我們還引號的目的是想做精準匹配,的確,早期的lucene的確是這麼做的。從返回結果上看,也的確達到了我們想要的記過——只返回“鋁合金輪轂”。但是我想告訴大家的是,這其實是早期Lucene的一個bug,在Lucene4中已經被修復了——將檢索詞加引號並不是實現精準匹配。
下面我們開始驗證。
{
"query": {
"term": {
"TITLE": "鋁合金輪轂"
}
}
}
我們使用term檢索來驗證,前面已經說到詞庫裏是沒有“鋁合金輪轂”這個詞的,也就是說分詞器不會將“鋁合金輪轂”作爲倒排的一個term進行索引。這樣來說,這個檢索必然是沒有數據返回,我們找一句話驗證一下。
GET _analyze?pretty
{
"analyzer": "ik_max_word",
"text": "某汽車鋁合金輪轂在使用一年後發現裂紋"
}
我們使用ik_max分詞,避免有些猩猩不服,下面是分詞結果:
{
"tokens": [
{
"token": "某",
"start_offset": 0,
"end_offset": 1,
"type": "CN_CHAR",
"position": 0
},
{
"token": "汽車",
"start_offset": 1,
"end_offset": 3,
"type": "CN_WORD",
"position": 1
},
{
"token": "鋁合金",
"start_offset": 3,
"end_offset": 6,
"type": "CN_WORD",
"position": 2
},
{
"token": "合金",
"start_offset": 4,
"end_offset": 6,
"type": "CN_WORD",
"position": 3
},
{
"token": "輪轂",
"start_offset": 6,
"end_offset": 8,
"type": "CN_WORD",
"position": 4
},
{
"token": "在",
"start_offset": 8,
"end_offset": 9,
"type": "CN_CHAR",
"position": 5
},
{
"token": "使用",
"start_offset": 9,
"end_offset": 11,
"type": "CN_WORD",
"position": 6
},
{
"token": "一年",
"start_offset": 11,
"end_offset": 13,
"type": "CN_WORD",
"position": 7
},
{
"token": "一",
"start_offset": 11,
"end_offset": 12,
"type": "TYPE_CNUM",
"position": 8
},
{
"token": "年後",
"start_offset": 12,
"end_offset": 14,
"type": "CN_WORD",
"position": 9
},
{
"token": "年",
"start_offset": 12,
"end_offset": 13,
"type": "COUNT",
"position": 10
},
{
"token": "後",
"start_offset": 13,
"end_offset": 14,
"type": "CN_CHAR",
"position": 11
},
{
"token": "發現",
"start_offset": 14,
"end_offset": 16,
"type": "CN_WORD",
"position": 12
},
{
"token": "裂紋",
"start_offset": 16,
"end_offset": 18,
"type": "CN_WORD",
"position": 13
}
]
}
從結果中可以發現,並沒有“鋁合金輪轂”這個詞,所以這個時候使用term檢索是不會檢索到這條數據的。那爲什麼用querySring就可以檢索的到呢?
因爲我們主觀帶入的認爲加上引號就是做了精準匹配,實際上早期的Lucene的確是這樣,但這是Lucene的一個bug,早已經被修復了,修復的結果是queryString中帶引號的檢索詞並不是使用精準匹配,而是使用短語匹配。
我們在使用Elasticsearch的過程中可能使用term和match的時候是最多的,用到短語匹配的可能並不是很多。但在特定的場景中,短語匹配的效果要遠好於term和match。
{
"query": {
"match_phrase" : {
"TITLE" : "鋁合金輪轂",
"slop": 0
}
}
}
match_phrase爲短語匹配,短語匹配同樣是先將檢索詞分詞,支持自定義分詞“analyzer”:“my_analyzer”,一般建議使用和索引分詞同一個分詞器的檢索效果更好。與match不同的是在於參數slop,官方給的解釋是
A phrase query matches terms up to a configurable slop (which defaults to 0) in any order. Transposed terms have a slop of 2.
翻大概意思就是允許分詞匹配順序錯誤的次數, “slop”: 0表示不允許順序錯誤,這樣即使將“鋁合金”和“輪轂”分開,但是結果中必須是“鋁合金”和“輪轂”挨着的纔會被檢索到,同樣可以實現term的檢索效果。
以上就是使用queryString檢索詞加不加引號,以及如何檢索精確檢索詞庫中不存在的詞的介紹。
附1
match檢索一直有一個通病,就是不支持多個檢索詞(例如terms),會報錯。網上找了很多資料,國內的各大網上均沒有相關介紹,最終在國外的網站找到了相似的:跳轉鏈接 和 github相關iss
{
"query": {
"match" : {
"TITLE" : ["鋁合金","輪轂"]
}
}
}
這種檢索式會報錯:
{
"error": {
"root_cause": [
{
"type": "illegal_state_exception",
"reason": "Can't get text on a START_ARRAY at 1:28"
}
],
"type": "illegal_state_exception",
"reason": "Can't get text on a START_ARRAY at 1:28"
},
"status": 500
}
所以我們可以這麼做:
{
"query": {
"bool": {
"must": [
{
"match": {
"TITLE": "鋁合金"
}
},
{
"match": {
"TITLE": "輪轂"
}
}
]
}
}
}
附2
前面提到match_phrase可以通過"slop": 0來實現term的效果,腦洞大開的猩猩們肯定會想到,那我是不是可以通過控制slop參數來實現match檢索的功能了呢?答案是不建議。雖然PhraseQuery也會進行打分,但是打分效果遠沒有BooleanQuery效果好,所以不建議這麼使用,無論是terms還是match比這都要好得多。
總結
本片文章介紹了Lucene的queryString檢索詞加不加引號的區別,以及如何使用term、match來進行檢索匹配。如何對詞庫中沒有的詞進行精確匹配,以及match不支持數組參數等問題進行探究。
作爲整個Elasticsearch乾貨系列的開篇,內容可能不多,但這的確是困擾了我很久的一個問題,在這裏分享給大家,主要還是相關資料太少。我們的老集羣已經運行很多年了, 還是0.9版本的集羣,那個年代的搜索引擎和現在差別太大了。在這裏跟一些初學者說,無論你是學習搜索還是Elasticsearch,一定要學習Lucene,不管Elasticsearch還是以前的solr,他們能夠強大,是因爲Lucene是全世界最好的搜索引擎,千遍萬遍底層技術十年不變。
後續會不定期更新干貨系列,非常希望大家能夠一起溝通,Elasticsearch越學你會越發現,它非常有趣。