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

和自甴很熟 提交于 2019-12-05 10:50:27

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.

You should also have an index on producttags over the two columns (productid, tag) in that order.

Another query I'm going to need to do is something like all (Car) or (Skateboard) or (Green AND Motorcycle) or (Red AND Motorcycle).

Sometimes these complex conditions are hard for the MySQL optimizer. One common workaround is to use UNION to combine simpler queries:

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.

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. 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:

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.

tigeryan

I would usually attack this by trying to eliminate records in the 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')
)
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!