1. 程式人生 > >ES[7.6.x]學習筆記(九)搜尋

ES[7.6.x]學習筆記(九)搜尋

搜尋是ES最最核心的內容,沒有之一。前面章節的內容,索引、動態對映、分詞器等都是鋪墊,最重要的就是最後點選搜尋這一下。下面我們就看看點選搜尋這一下的背後,都做了哪些事情。 ## 分數(score) ES的搜尋結果是按照相關分數的高低進行排序的,咦?! 怎麼沒說搜尋先說搜尋結果的排序了?咱們這裡先把這個概念提出來,因為在搜尋的過程中,會計算這個分數。這個分數代表了這條記錄匹配搜尋內容的相關程度。分數是一個浮點型的數字,對應的是搜尋結果中的`_score`欄位,分數越高代表匹配度越高,排序越靠前。 在ES的搜尋當中,分為兩種,一種計算分數,而另外一種是不計算分數的。 ## 查詢(query context) 查詢,代表的是這條記錄與搜尋內容匹配的怎麼樣,除了決定這條記錄是否匹配外,還要計算這條記錄的相關分數。這個和咱們平時的查詢是一樣的,比如我們搜尋一個關鍵詞,分詞以後匹配到相關的記錄,這些相關的記錄都是查詢的結果,那這些結果誰排名靠前,誰排名靠後呢?這個就要看匹配的程度,也就是計算的分數。 ## 過濾(filter context) 過濾,代表的含義非常的簡單,就是YES or NO,這條記錄是否匹配查詢條件,**它不會計算分數**。頻繁使用的過濾還會被ES加入到快取,以提升ES的效能。下面我們看一個查詢和過濾的例子,這個也是ES官網中的例子。 ```shell GET /_search { "query": { "bool": { "must": [ { "match": { "title": "Search" }}, { "match": { "content": "Elasticsearch" }} ], "filter": [ { "term": { "status": "published" }}, { "range": { "publish_date": { "gte": "2015-01-01" }}} ] } } } ``` 我們看一下請求的路徑`/_search`,這個是請求的路徑,而請求的方法是`GET`,我們再看請求體中,有一個`query`,這個代表著查詢的條件。而`bool`中的`must`被用作`query context`,它在查詢的時候會計算記錄匹配的相關分數。`filter`中的條件用作過濾,只會把符合條件的記錄檢索出來,不會計算分數。 ## 組合查詢 組合查詢包含了其他的查詢,像我們前面提到的`query context`和`filter context`。在組合查詢中,分為很多種型別,我們挑重點的型別給大家介紹一下。 ### Boolean Query boolean查詢,前面我們寫的查詢語句就是一個boolean查詢,boolean查詢中有幾個關鍵詞,表格如下: | 關鍵詞 | 描述 | | -------- | -------------------------------- | | must | 必須滿足的條件,而且會計算分數, | | filter | 必須滿足的條件,不會計算分數 | | should | 可以滿足的條件,會計算分數 | | must_not | 必須不滿足的條件,不會計算分數 | 我們看看下面的查詢語句: ```shell POST _search { "query": { "bool" : { "must" : { "term" : { "user" : "kimchy" } }, "filter": { "term" : { "tag" : "tech" } }, "must_not" : { "range" : { "age" : { "gte" : 10, "lte" : 20 } } }, "should" : [ { "term" : { "tag" : "wow" } }, { "term" : { "tag" : "elasticsearch" } } ], "minimum_should_match" : 1, "boost" : 1.0 } } } ``` 上面的查詢是一個典型的boolean組合查詢,裡邊的關鍵詞都用上了。很多小夥伴們可能對`must`和`should`的區別不是很瞭解,`must`是必須滿足的條件,我們的例子中`must`裡只寫了一個條件,如果是多個條件,那麼裡邊的所有條件必須滿足。而`should`就不一樣了,`should`裡邊現在列出了兩個條件,並不是說這兩個條件必須滿足,到底需要滿足幾個呢?我們看一下下面的關鍵字`minimum_should_match`,從字面上我們就可以看出它的含義,最小`should`匹配數,在這裡設定的是1,也就是說,`should`裡的條件只要滿足1個,就算匹配成功。在boolean查詢中,如果存在一個`should`條件,而沒有`filter`和`must`條件的話,那麼`minimum_should_match`的預設值是1,其他情況預設值是0。 我們再看一個實際的例子吧,還記得前面我們建立的`ik_index`索引嗎?索引中存在著幾條資料,資料如下: | _index | _type | _id | ▲_score | id | title | desc | | :------- | :---- | :------------------- | :------ | :--- | :---- | :--------- | | ik_index | _doc | fEsN-HEBZl0Dh1ayKWZb | 1 | 1 | 蘋果 | 蘋果真好吃 | | ik_index | _doc | 2 | 1 | 1 | 香蕉 | 香蕉真好吃 | | ik_index | _doc | 1 | 1 | 1 | 香蕉 | 香蕉真好吃 | | ik_index | _doc | 3 | 1 | 1 | 橘子 | 橘子真好吃 | | ik_index | _doc | 4 | 1 | 1 | 桃子 | 桃子真好吃 | 只有5條記錄,我們新建一個查詢語句,如下: ```shell POST /ik_index/_search { "query":{ "bool":{ "must":[ { "match":{ "desc":"香蕉好吃" } } ] } }, "from":0, "size":10, } ``` 我們查詢的條件是`desc`欄位滿足`香蕉好吃`,由於我們使用的ik分詞器,查詢條件`香蕉好吃`會被分詞為`香蕉`和`好吃`,但是5的資料的desc中都有`好吃`欄位,所有5條資料都會被查詢出來,我們執行一下,看看結果: | _index | _type | _id | ▲_score | id | title | desc | | :------- | :---- | :------------------- | :--------- | :--- | :---- | :--------- | | ik_index | _doc | 2 | 0.98773474 | 1 | 香蕉 | 香蕉真好吃 | | ik_index | _doc | 1 | 0.98773474 | 1 | 香蕉 | 香蕉真好吃 | | ik_index | _doc | 3 | 0.08929447 | 1 | 橘子 | 橘子真好吃 | | ik_index | _doc | 4 | 0.08929447 | 1 | 桃子 | 桃子真好吃 | | ik_index | _doc | fEsN-HEBZl0Dh1ayKWZb | 0.07893815 | 1 | 蘋果 | 蘋果真好吃 | 哈哈,5條資料全部查詢出來了,和我們的預期是一樣的,但是,我們需要注意一點的是`_score`欄位,它們的分數是不一樣的,我們的查詢條件是`香蕉好吃`,所以既包含`香蕉`又包含`好吃`的資料分數高,我們看到分數到了0.98,而另外3條資料只匹配了`好吃`,所以分數只有0.7,0.8。 ### Boosting Query 這個查詢比較有意思,它有兩個關鍵詞`positive`和`negative`,`positive`是“正”,所有滿足`positive`條件的資料都會被查詢出來,`negative`是“負”,**滿足`negative`條件的資料並不會被過濾掉**,而是會**扣減分數**。那麼扣減分數要扣減多少呢?這裡邊有另外一個欄位`negative_boost`,這個欄位是得分的係數,它的分數在0~1之間,滿足了`negative`條件的資料,它們的分數會乘以這個係數,比如這個係數是0.5,原來100分的資料如果滿足了`negative`條件,它的分數會乘以0.5,變成50分。我們看看下面的例子, ```shell POST /ik_index/_search { "query": { "boosting": { "positive": { "term": { "desc": "好吃" } }, "negative": { "term": { "desc": "香蕉" } }, "negative_boost": 0.5 } } } ``` `positive`條件是好吃,只要`desc`中有“好吃”的資料都會被查詢出來,而`negative`的條件是香蕉,只要`desc`中包含“香蕉”的資料都會被扣減分數,扣減多少分數呢?它的得分將會變為`原分數*0.5`。我們執行一下,看看效果, | index | type | _id | score | _source.id | source.title | source.desc | | :------- | :--- | :------------------- | :---------- | :--------- | :----------- | :---------- | | ik_index | _doc | 3 | 0.08929447 | 1 | 橘子 | 橘子真好吃 | | ik_index | _doc | 4 | 0.08929447 | 1 | 桃子 | 桃子真好吃 | | ik_index | _doc | fEsN-HEBZl0Dh1ayKWZb | 0.07893815 | 1 | 蘋果 | 蘋果真好吃 | | ik_index | _doc | 2 | 0.044647235 | 1 | 香蕉 | 香蕉真好吃 | | ik_index | _doc | 1 | 0.044647235 | 1 | 香蕉 | 香蕉真好吃 | 我們可以看到前3條資料的分數都在0.09左右,而後兩條的資料在0.044左右,很顯然,後兩條資料中的`desc`包含`香蕉`,它們的得分會乘以0.5的係數,所以分數只有前面資料的分數的一半。 ### 全文檢索 在前面幾節的內容中,我們介紹過,只有欄位的型別是`text`,才會使用全文檢索,全文檢索會使用到分析器,在我們的`ik_index`索引中,`title`和`desc`欄位都是text型別,所以,這兩個欄位的搜尋都會使用到ik中文分詞器。全文檢索比起前面的組合檢索要簡單一點,當然,在ES的官方文件中,全文檢索中的內容還是挺多的,在這裡我們只介紹一個標準的全文檢索。 我們看看下面的語句, ```shell POST /ik_index/_search { "query": { "match": { "desc": { "query": "蘋果" } } } } ``` 在請求體中,`match`代替了之前的`bool`,`match`是標準的全文索引的查詢。`match`後面跟的欄位是要查詢的欄位名,在咱們的例子中,查詢的欄位是`desc`,如果有多個欄位,可以列舉多個。`desc`欄位裡,`query`就是要查詢的內容。我們還可以在欄位中指定分析器,使用`analyzer`關鍵字,如果不指定,預設就是索引的分析器。我們執行一下上面的查詢,結果如下: | index | type | _id | score | source.id | source.title | source.desc | | :------- | :--- | :------------------- | :-------- | :-------- | :----------- | :---------- | | ik_index | _doc | fEsN-HEBZl0Dh1ayKWZb | 1.2576691 | 1 | 蘋果 | 蘋果真好吃 | 我們可以看到相應的資料已經檢索出來了。 ## 最後 在ES中,檢索的花樣是比較多的,這裡也不能一一給大家介紹了,只介紹一些最基本、最常用的查詢功能。下一篇我們看一下ES的聚合查詢