[英]mysql one-to-many query with negation and/or multiple criteria
由于关系数据库的性质,我认为这样的查询会很容易,但它似乎让我很合适。 我也四处寻找,但没有发现任何真正有帮助的东西。 这是情况:
假设我对产品和产品标签有一个简单的关系。 这是一个一对多的关系,所以我们可以有以下内容:
productid | tag
========================
1 | Car
1 | Black
1 | Ford
2 | Car
2 | Red
2 | Ford
3 | Car
3 | Black
3 | Lexus
4 | Motorcycle
4 | Black
5 | Skateboard
5 | Black
6 | Skateboard
6 | Green
查询所有(Ford OR Black OR Skateboard) AND NOT (Motorcycles OR Green)
的最有效方法是什么? 我需要做的另一个查询是 all (Car) or (Skateboard) or (Green AND Motorcycle) or (Red AND Motorcycle)
。
products 表中有大约 150k 条记录,tags 表中有 600k 条记录,因此查询需要尽可能高效。 这是我一直在处理的一个查询(示例#1),但它似乎需要大约 4 秒左右。 任何帮助将非常感激。
SELECT p.productid
FROM products p
JOIN producttags tag1 USING (productid)
WHERE p.active = 1
AND tag1.tag IN ( 'Ford', 'Black', 'Skatebaord' )
AND p.productid NOT IN (SELECT productid
FROM producttags
WHERE tag IN ( 'Motorcycle', 'Green' ));
到目前为止,我发现的最快的查询是这样的。 它需要 100-200 毫秒,但它似乎非常不灵活和丑陋。 基本上,我正在抓取所有与Ford
、 Black
或Skateboard
匹配的产品。 他们我将那些匹配产品的所有标签连接到一个以冒号分隔的字符串中,并删除所有匹配:Green:
AND :Motorcycle:
。 有什么想法吗?
SELECT p.productid,
Concat(':', Group_concat(alltags.tag SEPARATOR ':'), ':') AS taglist
FROM products p
JOIN producttags tag1 USING (productid)
JOIN producttags alltags USING (productid)
WHERE p.active = 1
AND tag1.tag IN ( 'Ford', 'Black', 'Skateboard' )
GROUP BY tag1.productid
HAVING ( taglist NOT LIKE '%:Motorcycle:%'
AND taglist NOT LIKE '%:Green:%' );
我会在没有子查询的情况下编写排除连接:
SELECT p.productid
FROM products p
INNER JOIN producttags AS t ON p.productid = t.productid
LEFT OUTER JOIN producttags AS x ON p.productid = x.productid
AND x.tag IN ('Motorcycle', 'Green')
WHERE p.active = 1
AND t.tag IN ( 'Ford', 'Black', 'Skateboard' )
AND x.productid IS NULL;
确保您按该顺序在两列(活动、产品 ID)上有关于产品的索引。
您还应该按该顺序在两列(productid、tag)上的 producttags 上建立索引。
我需要做的另一个查询是像 all (Car) or (Skateboard) or (Green AND Motorcycle) or (Red AND Motorcycle) 这样的查询。
有时这些复杂的条件对 MySQL 优化器来说是困难的。 一种常见的解决方法是使用 UNION 来组合更简单的查询:
SELECT p.productid
FROM products p
INNER JOIN producttags AS t1 ON p.productid = t1.productid
WHERE p.active = 1
AND t1.tag IN ('Car', 'Skateboard')
UNION ALL
SELECT p.productid
FROM products p
INNER JOIN producttags AS t1 ON p.productid = t1.productid
INNER JOIN producttags AS t2 ON p.productid = t2.productid
WHERE p.active = 1
AND t1.tag IN ('Motorcycle')
AND t2.tag IN ('Green', 'Red');
PS:您的标记表不是实体-属性-值表。
我会得到所有唯一的 ID 匹配和唯一的 ID 来过滤掉,然后 LEFT JOIN 这些列表(按照tigeryan)并过滤掉任何匹配的ID。 通过将所有查询分开,查询也应该更易于阅读和修改。 它也应该相当快,虽然它可能看起来不像。
SELECT * FROM products p
WHERE
p.active=1 AND
productid IN (
SELECT matches.productid FROM (
SELECT DISTINCT productid FROM producttags
WHERE tag IN ('Ford','Green','Skatebaord')
) AS matches
LEFT JOIN (
SELECT DISTINCT productid FROM producttags
WHERE tag IN ('Motorcycles','Green')
) AS filter ON filter.productid=matches.productid
WHERE filter.productid IS NULL
)
有时 JOIN 比 IN 快,这取决于 mysql 如何优化查询:
SELECT p.* FROM (
SELECT matches.productid FROM (
SELECT DISTINCT productid FROM producttags
WHERE tag IN ('Ford','Green','Skatebaord')
) AS matches
LEFT JOIN (
SELECT DISTINCT productid FROM producttags
WHERE tag IN ('Motorcycles','Green')
) AS filter ON filter.productid=matches.productid
WHERE filter.productid IS NULL
) AS idfilter
JOIN products p ON p.productid=idfilter.productid AND p.active=1
第二个查询应该强制连接顺序,因为必须首先完成内部选择。
我通常会通过尝试消除 from 中的记录来攻击这一点。
select p.productid
from product p
left join producttags tag1
on p.productid = tag1.productid and tag1.tag NOT IN ('Motorcycles','Green')
where tag1.tag IN ('Ford','Black','Skateboard') and p.active = 1
这个如何:
SELECT DISTINCT p.id FROM products AS p
JOIN producttags AS included ON (
included.productid = p.id
AND included.tag IN ('Ford', 'Black', 'Skatebaord')
)
WHERE active = 1
AND p.id NOT IN (
SELECT DISTINCT productid FROM producttags
WHERE tag IN ('Motorcycle', 'Green')
)
CONCAT/LIKE 解决方案的替代方案:
SELECT p.productid
FROM products p
JOIN producttags USING (productid)
WHERE p.active = 1
AND tag IN ('Ford', 'Black', 'Skateboard')
GROUP BY p.productid
HAVING SUM(IF(tag IN ('Motorcycle','Green'), 1, 0)) = 0;
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.