J'écrirais la jointure d'exclusion sans sous-requêtes :
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;
Assurez-vous d'avoir un index sur les produits sur les deux colonnes (active, productid) dans cet ordre.
Vous devriez également avoir un index sur les balises de produit sur les deux colonnes (productid, tag) dans cet ordre.
Une autre requête que je vais devoir faire est quelque chose comme tout (Voiture) ou (Skateboard) ou (Vert ET Moto) ou (Rouge ET Moto).
Parfois, ces conditions complexes sont difficiles pour l'optimiseur MySQL. Une solution de contournement courante consiste à utiliser UNION pour combiner des requêtes plus simples :
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 :Votre table de marquage n'est pas une table Entity-Attribute-Value.