有没有办法选择其中一列只包含但是包含任意数量的预定义值的行?
我一直在使用它,但它会返回我的列中至少包含一个值的任何行(这正是它所要做的,我知道)。
但我正在寻找一种方法,只选择关键字列中只包含关键字的行。
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
我的专栏包含一个逗号分隔的列表,列出了适用于该产品行的所有关键字。
答案 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来获取指定的结果。
执行您指定的检查的一种方法是创建一组所有可能的"匹配"并检查这些。这适用于几个关键字。例如,要获取仅包含关键字'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);