2014-04-03 62 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

相关问题