Elasticsearch乾貨(九):queryString中檢索詞加不加引號?

對於剛接觸搜索或者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檢索。那麼這樣的話就會帶來兩個問題:

  1. 如果我不想用match,我想精準匹配只返回帶“鋁合金輪轂”而不是“鋁合金”+“輪轂”怎麼辦?
  2. 如果詞庫裏沒有“鋁合金輪轂”這個詞,該怎麼做精準匹配?

說到這,可能有些人就會說了,你把“鋁合金輪轂”這個詞加上引號不就行了。我們來具體看看行還是不行:

{
  "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越學你會越發現,它非常有趣。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章