既然你存储列表中含有一个逗号分隔的列表的字符串,而不是作为一组,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