MySQL返回列中包含任何但仅包含集合中的关键字的所有行

时间:2014-04-03 20:13:46

标签: mysql sql rows mysql-workbench

有没有办法选择其中一列只包含但是包含任意数量的预定义值的行?

我一直在使用它,但它会返回我的列中至少包含一个值的任何行(这正是它所要做的,我知道)。

但我正在寻找一种方法,只选择关键字列中只包含关键字的行。

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 个答案:

答案 0 :(得分:2)

由于您将列表存储为包含逗号分隔列表的字符串,而不是作为一个集合,因此MySQL无法提供更多帮助。当它被插入到数据库中时,MySQL将其视为单个字符串。当从数据库中检索到它时,MySQL会将其视为单个字符串。当我们在查询中引用它时,MySQL将其视为单个字符串。


如果"列表"存储为标准关系集,产品的每个关键字都存储为表中的单独行,然后返回您指定的结果集几乎是微不足道的。

例如,如果我们有这个表:

CREATE TABLE product_keyword 
product_id      BIGINT UNSIGNED COMMENT 'FK ref products.id'
keyword         VARCHAR(20)

将与特定产品关联的每个关键字作为单独的行:

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

然后查找product中包含'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

要获得至少包含以下关键字之一的产品'巧克力'和' vanilla',但没有关联的其他关键字,它是上面相同的查询,但有一个额外的联接:

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子句中的谓词消除行有匹配的,留下没有匹配的产品的行集。


但是将该集存储为"以逗号分隔的列表"在单个字符列中,我们失去了关系代数的所有优点;没有任何简单的方法可以将关键字列表作为" set"来处理。

将整个列表存储为单个字符串,我们得到了一些可怕的SQL来获取指定的结果。

执行您指定的检查的一种方法是创建一组所有可能的&#34;匹配&#34;并检查这些。这适用于几个关键字。例如,要获取仅包含关键字'vanilla'和/或'chocolate'的产品列表(即,至少包含其中一个关键字且没有任何其他关键字):< / p>

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中提取的任意最大数量的关键字。

使用一些简单的SQL字符串函数从逗号分隔列表中提取第n个元素相当容易,例如,从逗号分隔列表中提取前五个元素:

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,返回两个&#34;示例&#34;行作为演示。该内联视图可以替换为对包含该表的内容的表的引用product_id和keyword_list列。)

因此,该查询为我们提供了一个行集,就像我从上面给出的product_keyword表中返回的那样。

在示例查询中,可以使用此查询替换对product_keyword表的引用。但这是一个非常丑陋的SQL,而且它的效率非常低,在运行查询时随时创建和填充临时MyISAM表。

答案 1 :(得分:1)

您可能希望在fulltext index上为您的表设置keywords。这允许您搜索关键字列并指定要包含或不包含的关键字。这是一个设置索引的命令:

ALTER TABLE products ADD FULLTEXT index_products_keywords (keywords);

完成后,您可以使用MATCH AGAINST词组并指定关键字。您可以像WHERE MATCH(keywords) AGAINST ('chocolate')一样使用它来搜索术语巧克力。或者您可以使用BOOLEAN MODE来关闭&#34;关闭&#34;某些关键字。

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

Here's a small tutorial about fulltext indexes