既然你存儲列表中含有一個逗號分隔的列表的字符串,而不是作爲一組,MySQL是不會能夠幫助很多這一點。當它被插入數據庫時,MySQL將其視爲單個字符串。從數據庫中檢索時,MySQL將其視爲單個字符串。當我們在查詢中引用它時,MySQL將其視爲單個字符串。
如果「列表」被存儲爲標準的關係組,每個關鍵字存儲爲表中的一個單獨的行一個產品,然後將結果返回指定設置幾乎是微不足道的。
例如,如果我們有這個表:
CREATE TABLE product_keyword
product_id BIGINT UNSIGNED COMMENT 'FK ref products.id'
keyword VARCHAR(20)
相關聯的特定產品作爲一個單獨的行中的每個關鍵字:在product
product_id keyword
---------- ---------
1 chocolate
1 sugar
2 chocolate
3 bran
3 chocolate
3 milk
3 oats
3 sugar
4 chocolate
4 salt
4 sugar
然後找到的所有行有一個關鍵字'chocolate'
或'vanilla'
SELECT p.id
FROM product p
JOIN product_keyword k
WHERE k.product_id = p.id
ON k.keyword NOT IN ('chocolate','vanilla')
GROUP BY p.id
- 或 -
SELECT p.id
FROM product p
LEFT
JOIN (SELECT j.id
FROM product_keyword j
WHERE j.keyword NOT IN ('chocolate','vanilla')
GROUP BY j.id
) k
ON k.id = p.id
WHERE k.id IS NULL
要獲得有關鍵字「巧克力」和「香草」的至少一個產品,而是有關聯的其他關鍵字,這是相同的查詢之上,但與加入:
SELECT p.id
FROM product p
JOIN (SELECT g.id
FROM product_keyword g
WHERE g.keyword IN ('chocolate','vanilla')
GROUP BY g.id
) h
ON h.id = p.id
LEFT
JOIN (SELECT j.id
FROM product_keyword j
WHERE j.keyword NOT IN ('chocolate','vanilla')
GROUP BY j.id
) k
ON k.id = p.id
WHERE k.id IS NULL
我們可以解壓那些查詢,它們並不難。查詢h
返回至少包含一個關鍵字的product_id列表,查詢k
返回一個product_id列表,其中包含除指定關鍵字以外的其他關鍵字。那裏的「訣竅」(如果你想這樣稱呼的話)就是反連接模式......做一個外連接來匹配行,並且包含沒有匹配的行和WHERE子句中的謂詞消除具有匹配的行,從沒有匹配的產品留下一組行。
但隨着存儲爲單個字符列中的「逗號分隔的列表」設置的,我們失去了關係代數的所有優點;沒有任何簡單的方法可以將關鍵字列表作爲「集合」進行處理。
整個列表存儲爲一個字符串,我們有一些可怕的SQL來獲得指定的結果。
做你指定檢查的一種方法是創建一組所有可能的「匹配」,並檢查這些。這適用於幾個關鍵字。例如,爲了獲得僅具有關鍵字'vanilla'
和/或'chocolate'
的產品列表,(即,有這些關鍵字中的至少一個,並沒有任何其他關鍵字):
SELECT p.id
FROM product
WHERE keyword_list = 'chocolate'
OR keyword_list = 'vanilla'
OR keyword_list = 'chocolate,vanilla'
OR keyword_list = 'vanilla,chocolate'
但延長(除非關鍵字保證以特定順序出現),並且很難檢查四個關鍵字中的三個關鍵字
另一個(醜陋的)方法是轉換keyword_list
爲一個集合,這樣我們就可以在我的答案中使用類似於第一個查詢的查詢。但是執行轉換的SQL受限於任意最大數目可以從keyword_list中提取的關鍵字。
這是相當容易提取的逗號分隔列表的第n個元素,使用一些簡單的SQL字符串函數,例如,提取從逗號第一五行分隔列表:
SET @l := 'chocolate,sugar,bran,oats'
SELECT NULLIF(SUBSTRING_INDEX(CONCAT(@l,','),',',1),'') AS kw1
, NULLIF(SUBSTRING_INDEX(SUBSTRING_INDEX(CONCAT(@l,','),',',2),',',-1),'') AS kw2
, NULLIF(SUBSTRING_INDEX(SUBSTRING_INDEX(CONCAT(@l,','),',',3),',',-1),'') AS kw3
, NULLIF(SUBSTRING_INDEX(SUBSTRING_INDEX(CONCAT(@l,','),',',4),',',-1),'') AS kw4
, NULLIF(SUBSTRING_INDEX(SUBSTRING_INDEX(CONCAT(@l,','),',',5),',',-1),'') AS kw5
但這些都是仍然在同一行。如果我們想對這些進行檢查,我們會做一些比較,我們需要檢查其中的每一個,看它是否在指定的列表中。
如果我們可以將這些關鍵字在一行中轉換爲一行,每行有一個關鍵字的行,那麼我們可以使用我的答案中的第一個關鍵字的查詢。舉個例子:
SELECT t.product_id
, NULLIF(CASE n.i
WHEN 1 THEN SUBSTRING_INDEX(CONCAT(t.l,','),',',1)
WHEN 2 THEN SUBSTRING_INDEX(SUBSTRING_INDEX(CONCAT(t.l,','),',',2),',',-1)
WHEN 3 THEN SUBSTRING_INDEX(SUBSTRING_INDEX(CONCAT(t.l,','),',',3),',',-1)
WHEN 4 THEN SUBSTRING_INDEX(SUBSTRING_INDEX(CONCAT(t.l,','),',',4),',',-1)
WHEN 5 THEN SUBSTRING_INDEX(SUBSTRING_INDEX(CONCAT(t.l,','),',',5),',',-1)
END,'') AS kw
FROM (SELECT 4 AS product_id,'fee,fi,fo,fum' AS l
UNION ALL
SELECT 5, 'coffee,sugar,milk'
) t
CROSS
JOIN (SELECT 1 AS i
UNION ALL SELECT 2
UNION ALL SELECT 3
UNION ALL SELECT 4
UNION ALL SELECT 5
) n
HAVING kw IS NOT NULL
ORDER BY t.product_id, n.i
這就使我們各行,但它僅限於一排各前5個關鍵字。很容易看出這將如何延長(具有n返回6,7,8,...)並延長CASE中的WHEN條件以處理6,7,8 ...
但是,是一些武斷的限制。 (我使用了一個內聯視圖,別名爲t
,以返回兩個「示例」行作爲演示。內聯視圖可以替換爲包含product_id和keyword_list列的表的引用。)
So ,那個查詢就會從我上面給出的product_keyword
表中返回一個行集。
在示例查詢中,可以用此查詢替換對product_keyword
表的引用。但是,這是一大堆醜陋的SQL,而且它的效率非常低,在任何時候運行查詢時都會創建並填充臨時MyISAM表。
這是完美的!我可以使用正確的結構輕鬆地重新創建表格以實現此功能。謝謝!我不確定如何在創建表格時存儲關鍵字,但我應該能夠正確創建它們以使其工作得最好。 – loopifnil
它看起來像我有太多的set()數據類型的選項... – loopifnil
@loopifnil:只是要清楚,我沒有提到MySQL的「SET」數據類型。通過「設置」,我只是指一個表中的「一組行」,每行代表一個產品的一個關鍵字。這與包含字符串的單個行相反。 (而不是''SET''數據類型沒有一些性能優勢,它確實有,但它僅限於有效值的靜態列表;並且它具有缺點,因爲字符串中的逗號分隔列表具有作爲行處理。 – spencer7593