大多數開發者應該都遇到過在mysql字段中存儲逗號分割字符串的經歷,不管這些被分割的字段表明的是id仍是tag,這個字段都應該具備以下幾個共性。php
被分割的字段必定是有限並且數量較少的,咱們不可能在一個字符串中存儲無限多個字符java
這個字段所屬的表與這個字段關聯的表,必定是一對多的關係mysql
好比下面這個表結構所表明的content與tag這兩個對象sql
mysql> SELECT * FROM content; +----+------+ | id | tags | +----+------+ | 1 | 1,2 | | 2 | 2,3 | +----+------+ 2 rows in set (0.01 sec) mysql> SELECT * FROM tag; +----+-------+ | id | name | +----+-------+ | 1 | php | | 2 | mysql | | 3 | java | +----+-------+ 3 rows in set (0.00 sec)
這些原則問題,相信你們在開發過程當中已經很熟悉了。可是你在使用這種方法來處理實際問題時,心裏必定仍是有些許忐忑,由於這種方法或多或少看上去有點像野路子。在那本厚厚的《數據庫》教材中,也沒有提到這種設計方法,標準的方法彷佛是應該使用一個關係映射表在這兩個表之間插一槓子,儘管這樣會使用效率低下的鏈接查詢。數據庫
每一個開發者都曾糾結於標準與效率,但我想咱們的努力能使這種方法的使用看起來更加標準。注意,如下討論的使用方法僅限於mysql,但其它數據庫應該能夠移植。bash
<h3>相關性檢索</h3>函數
不少開發者還在使用古老的LIKE方法來實現相關性檢索,好比上面那個數據庫結構中,content表中的兩條記錄都有2這個tag,那麼怎樣在我取出記錄1時,把與它tag相關的記錄也顯示出來呢。其實這也是CMS須要面對的一個基本問題,也就是相關內容的查詢。性能
若是你是一個菜鳥,你可能只會想到LIKE方法,好比先把記錄1取出來,而後再把tags字段按逗號分割,最後作一個循環用LIKE檢索content表中全部tags字段中包含2的記錄,相似這樣測試
SELECT * FROM content WHERE tag LIKE '%2%' AND id <> 1
但這種方法實在是太慢了,查詢次數多不說,LIKE查詢原本就是一個比較慢的方法。並且你還要處理先後逗號的問題,總之麻煩是一大堆。搜索引擎
因此讓咱們靜下心來翻翻mysql手冊,看看有沒有什麼驚喜。這個時候,一個名爲FIND_IN_SET的函數,會閃着金光映入你的眼簾。讓咱們看看這個函數的定義
<blockquote>
FIND_IN_SET(str,strlist)
Returns a value in the range of 1 to N if the string str is in the string list strlist consisting of N substrings. A string list is a string composed of substrings separated by 「,」 characters. If the first argument is a constant string and the second is a column of type SET, the FIND_IN_SET() function is optimized to use bit arithmetic. Returns 0 if str is not in strlist or if strlist is the empty string. Returns NULL if either argument is NULL. This function does not work properly if the first argument contains a comma (「,」) character.
</blockquote>
哦,PERFECT! 簡單說來就是尋找一個字符串是否在另外一個以逗號分割的字符串中存在的函數,這簡直是爲咱們量身定作的。那麼咱們的sql就變成
SELECT * FROM content WHERE FIND_IN_SET('2', tags) AND id <> 1
在翻這些函數的過程當中,你應該已經深深地體會到mysql的設計者對以逗號分割存儲字段方法的確定,由於有不少方法就是設計用來處理這種問題的。
這樣看起來好多了,一切彷佛完美了,是這樣嗎?其實尚未,若是你的tag比較多,你須要建立多個sql語句,並且有的記錄關聯的tag比較多,有的比較少,怎麼能按照相關性進行排列呢。
這個時候,你能夠關注mysql的全文檢索功能。這個詞你確定看見過無數回了,可是這麼使用的確定不多,讓咱們直接看語句吧
SELECT * FROM content WHERE MATCH(tags) AGAINST('1,2') AND id <> 1
這個語句的優點是顯而易見的,你不須要對tags字段作再次分割。那麼這種查詢的原理是什麼呢,稍微瞭解下MATCH AGAINST的用法就知道,全文檢索的默認分隔符是標點符號和stopwords,其中前者正是咱們須要的特性。全文檢索按照逗號將MATCH和AGAINST裏的字符串作分割,而後將它們匹配。
須要注意的是上面sql僅僅是個例子,若是你直接這麼執行,是沒法獲得任何結果的。緣由在如下
<ol>
<li>你須要對tags字段創建fulltext索引(若是僅僅是測試,能夠不作,建索引只是提升性能,對結果沒有影響)</li>
<li>每一個被標點符號分割的word長度必須在3個字符以上,這纔是關鍵,咱們的tag id過短了,會被自動忽略掉,這個時候你能夠考慮讓id從一個比較大值開始自增,好比1000,這樣它就夠長了。</li>
<li>你撞到了stopwords,好比你的tags字段是這樣的'hello,nobody',nobody是mysql的一個默認的stop words,它會被自動忽略。stop words是英文中的一些無心義詞,搜索的時候不須要它們,相似漢語中的助詞等等。但在咱們的使用中顯然不是用來作搜索的,所以能夠在my.cnf文件裏,加上ft_stopword_file=''來禁用它</li>
</ol>
隨着WEB技術的發展,相關搜索走SQL的狀況愈來愈少,不少時候只須要用搜索引擎就能夠了。但本文的目的並不僅是討論這種方法,而是體現實現這一結果的過程。