简体   繁体   English

带有否定和/或多个条件的mysql一对多查询

[英]mysql one-to-many query with negation and/or multiple criteria

I thought a query like this would be pretty easy because of the nature of relational databases but it seems to be giving me a fit.由于关系数据库的性质,我认为这样的查询会很容易,但它似乎让我很合适。 I also searched around but found nothing that really helped.我也四处寻找,但没有发现任何真正有帮助的东西。 Here's the situation:这是情况:

Let's say I have a simple relationship for products and product tags.假设我对产品和产品标签有一个简单的关系。 This is a one-to-many relationship, so we could have the following:这是一个一对多的关系,所以我们可以有以下内容:

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

What's the most efficient way to query for all (Ford OR Black OR Skateboard) AND NOT (Motorcycles OR Green) ?查询所有(Ford OR Black OR Skateboard) AND NOT (Motorcycles OR Green)最有效方法是什么? Another query I'm going to need to do is something like all (Car) or (Skateboard) or (Green AND Motorcycle) or (Red AND Motorcycle) .我需要做的另一个查询是 all (Car) or (Skateboard) or (Green AND Motorcycle) or (Red AND Motorcycle)

There are about 150k records in the products table and 600k records in the tags tables, so the query is going to need to be as efficient as possible. products 表中有大约 150k 条记录,tags 表中有 600k 条记录,因此查询需要尽可能高效。 Here's one query that I've been messing around with (example #1), but it seems to be taking about 4 seconds or so.这是我一直在处理的一个查询(示例#1),但它似乎需要大约 4 秒左右。 Any help would be much appreciated.任何帮助将非常感激。

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' ));

Update更新

The quickest query I've found so far is something like this.到目前为止,我发现的最快的查询是这样的。 It's taking 100-200ms but it seems pretty inflexible and ugly.它需要 100-200 毫秒,但它似乎非常不灵活和丑陋。 Basically I'm grabbing all products that match Ford , Black , or Skateboard .基本上,我正在抓取所有与FordBlackSkateboard匹配的产品。 Them I'm concatenating all of the tags for those matched products into a colon-separated string and removing all products that match on :Green: AND :Motorcycle: .他们我将那些匹配产品的所有标签连接到一个以冒号分隔的字符串中,并删除所有匹配:Green: AND :Motorcycle: Any thoughts?有什么想法吗?

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:%' ); 

I'd write the exclusion join with no subqueries:我会在没有子查询的情况下编写排除连接:

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;

Make sure you have an index on products over the two columns (active, productid) in that order.确保您按该顺序在两列(活动、产品 ID)上有关于产品的索引。

You should also have an index on producttags over the two columns (productid, tag) in that order.您还应该按该顺序在两列(productid、tag)上的 producttags 上建立索引。

Another query I'm going to need to do is something like all (Car) or (Skateboard) or (Green AND Motorcycle) or (Red AND Motorcycle).我需要做的另一个查询是像 all (Car) or (Skateboard) or (Green AND Motorcycle) or (Red AND Motorcycle) 这样的查询。

Sometimes these complex conditions are hard for the MySQL optimizer.有时这些复杂的条件对 MySQL 优化器来说是困难的。 One common workaround is to use UNION to combine simpler queries:一种常见的解决方法是使用 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: Your tagging table is not an Entity-Attribute-Value table. PS:您的标记表不是实体-属性-值表。

I would get all the unique ID matches and the unique IDs to filter out, then LEFT JOIN those lists (as per tigeryan) and filter out any IDs that match.我会得到所有唯一的 ID 匹配和唯一的 ID 来过滤掉,然后 LEFT JOIN 这些列表(按照tigeryan)并过滤掉任何匹配的ID。 The query should also be easier to read and modify by keeping all the queries separate.通过将所有查询分开,查询也应该更易于阅读和修改。 It should be fairly quick also, although it may not look like it.它也应该相当快,虽然它可能看起来不像。

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
)

Sometimes a JOIN is faster than an IN, depending on how mysql optimizes the query:有时 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

The second query should force the join order since the internal selects have to be done first.第二个查询应该强制连接顺序,因为必须首先完成内部选择。

I would usually attack this by trying to eliminate records in the from...我通常会通过尝试消除 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

What about this one:这个如何:

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')
)

Alternative to the CONCAT/LIKE solution: 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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM