百度二面,MySQL 怎么做權重搜索?
考慮這樣一個搜索需求,有一個 MySQL 表,表中很多個列存放著不同的內容,希望用戶通過關鍵詞進行搜索的時候,能夠模糊匹配多個列,比如有 t1 列、t2 列、t3 列,同時還希望 t1 列的匹配權重最高,t3 列的匹配權重最低。簡單說就是如果有一個關鍵詞同時出現在記錄 A 的 t1 列 和里記錄 B 的 t2 列,那么記錄 A 應該優先展示,排序是在前面的。
注意這里是 MySQL,不是 ES,搜索引擎做這種搜索需求當然得天獨厚,但是這種在 MySQL 中進行權重搜索的需求也不是沒有,業務初期數據量還不大的時候大概率不會考慮上 ES,這時候在 MySQL 中先簡單跑通邏輯才是最重要的。
思考下該如何做?
模糊匹配
首先模糊匹配大家最常用的就是 like
:
SELECT * FROM test
WHERE t1 LIKE '%標題%'
or t2 LIKE '%內容%'
or t3 LIKE '%注釋%''
當只需要簡單的模式匹配時 like
確實往往是更好的選擇。而在需要進行復雜匹配,如同一字段中包含多個模式,進行分組匹配等,REGEXP
則表現更為突出,Mysql 支持對列的正則表達式方式查詢,使用方式如下:
SELECT * FROM test
WHERE t1 regexp '標題'
or t2 regexp '內容'
or t3 regexp '注釋'
權重搜索
權重搜索涉及到幾個 Mysql 函數。
LOCATE('標題', test.t1)
: 查詢 "標題" 在 test.t1 列出現的位置,0 表示未找到。否則返回 坐標位置,坐標位置從 1 開始。IF( 表達式, 1, 0)
:判斷表達式結果,TRUE 則返回 1,FALSE 則返回 0
下面我們來看如何基于這兩個函數來實現文章開頭的需求:
SELECT *, (
IF(LOCATE('標題',test.t1), 1, 0)
+ IF(LOCATE('內容',test.t2) , 1, 0)
+ IF(LOCATE('注釋',test.t3) , 1, 0)
) AS weight
FROM test
WHERE test.t1 regexp '標題'
or test.t2 regexp '內容'
or test.t3 regexp '注釋'
order by weight desc
上面的查詢中,每個關鍵詞的權重都是 1,所以,在這 t1\t2\t3 三列中,關鍵詞出現次數最多的記錄將出現在搜索結果的第一位。
如果權重增加,那么權重高的關鍵詞將會影響排序規則。如下例子,將 t1 列的搜索權重改為 7:
SELECT *, (
IF(LOCATE('標題',test.t1), 7, 0)
+ IF(LOCATE('內容',test.t2) , 2, 0)
+ IF(LOCATE('注釋',test.t3) , 1, 0)
) AS weight
FROM test
WHERE test.t1 regexp '標題'
or test.t2 regexp '內容'
or test.t3 regexp '注釋'
order by weight desc