2014-04-03 109 views
3

有沒有方法可以選擇其中一列只包含任意數量的預定義值的行?MySQL返回列中包含任何關鍵字但不包含任何關鍵字的所有行

我一直在使用它,但它返回的任何行中我的列至少包含一個值(這正是它應該做的,我知道)。

但我正在尋找一種方法來只選擇那些在關鍵字列中只有我的關鍵字的行。

SELECT * 
FROM 
    `products`.`product` 
WHERE 
    keywords LIKE '%chocolate%' 
AND keyword LIKE '%vanilla%'; 

舉例關鍵詞:chocolate, sugar, milk, oats

使用上面的關鍵詞,我想前兩個返回的結果,但不是最後兩個:

Product1: chocolate, sugar 

Product2: chocolate 

Product3: chocolate, sugar, milk, oats, bran 

Product4: chocolate, sugar, salt 

我列包含逗號分隔的列表所有適用於該產品行的關鍵字。

回答

2

既然你存儲列表中含有一個逗號分隔的列表的字符串,而不是作爲一組,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表。

+0

這是完美的!我可以使用正確的結構輕鬆地重新創建表格以實現此功能。謝謝!我不確定如何在創建表格時存儲關鍵字,但我應該能夠正確創建它們以使其工作得最好。 – loopifnil

+0

它看起來像我有太多的set()數據類型的選項... – loopifnil

+0

@loopifnil:只是要清楚,我沒有提到MySQL的「SET」數據類型。通過「設置」,我只是指一個表中的「一組行」,每行代表一個產品的一個關鍵字。這與包含字符串的單個行相反。 (而不是''SET''數據類型沒有一些性能優勢,它確實有,但它僅限於有效值的靜態列表;並且它具有缺點,因爲字符串中的逗號分隔列表具有作爲行處理。 – spencer7593

1

您可能想要爲您的表設置fulltext index,keywords。這允許您搜索關鍵字列並指定包含或不包含的關鍵字。下面是其中規定了指數的命令:

ALTER TABLE products ADD FULLTEXT index_products_keywords (keywords); 

一旦你做到了這一點,你可以用短語MATCH AGAINST,並指定關鍵字。您可以像WHERE MATCH(keywords) AGAINST ('chocolate')那樣使用它來搜索術語巧克力。或者,您可以使用BOOLEAN MODE「關閉」某些關鍵字。

SELECT * FROM products 
WHERE MATCH(keywords) AGAINST ('+chocolate -bran' IN BOOLEAN MODE); 

Here's a small tutorial about fulltext indexes

+0

會有一種方法可以關閉所有關鍵字,但用戶輸入的是少數關鍵字嗎?我的關鍵字數據庫非常大。 – loopifnil

+0

我不認爲它是這樣的。如果您以某種方式關閉所有關鍵字,然後搜索「巧克力」,則只會顯示一行關鍵字爲「巧克力」的行。 – Grashlok

+0

這就是我想要發生的事情。我想讓用戶提供關鍵字列表,然後僅返回僅包含所提供列表中的關鍵字的產品,但返回任意數量的關鍵字。 – loopifnil

相關問題