2009-02-12 5 views
1

exclus J'ai une instruction SELECT qui fonctionne, et fonctionne assez vite sur mes tables (< 0.01sec sur 50k + produits, 3k + catégories). Mais dans mon esprit, ce n'est pas très élégant et j'aimerais entendre des suggestions pour l'améliorer.SQL: Sélectionnez les enregistrements appartenant à la catégorie exclus qui appartiennent uniquement à la catégorie

Il y a 3 tables d'intérêt:

  • produits - ProductID clés
  • catégories - clés categoryID
  • products_tree - table de liens (catégories contiennent de nombreux produits, les produits peuvent appartenir à plusieurs catégories)

J'ai une liste des categoryIDs exclus [par exemple 1040,1050,1168] Je veux sélectionner tous les productIDs qui appartiennent à l'une de ces catégories exclues que si le produit ne fait pas partie à une autre catégorie non exclus

Ma requête ressemble à ceci:

SELECT DISTINCT productID 
FROM products_tree 
WHERE 
    categoryID IN (1040,1050,1168) 
    AND productID NOT IN 
    (SELECT DISTINCT productID 
     FROM products_tree 
     WHERE 
     categoryID NOT IN (1040,1050,1168) 
    ); 

Répondre

1

Je peux penser à quelques-unes méthodes, dont chacune fonctionne différemment en fonction des index et de votre implémentation de base de données particulière. Certains peuvent sembler lents et peuvent être optimisés d'une manière que vous n'avez peut-être pas imaginée. Il vaut donc mieux les essayer et comparer les plans d'exécution pour voir ce qui se passe ...

Note1: J'utilise GROUP BY plutôt que DISTINCT, c'est car cela permet à l'optimiseur d'utiliser des index. J'ai vu des implémentations travailler que ils peuvent tourner le DISTINCT pour GROUP BY, mais il est très intéressant d'utiliser GROUP BY à la place de poing pour être sûr. Cela vous amène aussi à penser aux index, ce qui n'est jamais une mauvaise chose.

Note2: Certaines requêtes comme celle-ci prennent un certain temps pour optimiser, car il y a beaucoup d'options pour l'optimisateur d'évaluer. Il est donc souvent utile de compiler toutes les différentes options pour les procédures stockées et en comparant l'exécution de ces procédures stockées. Cela garantit que vous comparez réellement le temps de requête et pas les temps de compilation différents.

SELECT 
    [tree].productID 
FROM 
    products_tree AS [tree] 
WHERE 
    [tree].productID IN (1040,1050,1168) 
    AND NOT EXISTS (SELECT * FROM products_tree WHERE productID = [tree].productID AND categoryID NOT IN (1040,1050,1168)) 
GROUP BY 
    [tree].productID 


SELECT 
    [tree].productID 
FROM 
    products_tree AS [tree] 
LEFT OUTER JOIN 
    (
     SELECT 
     productID 
     FROM 
     product_tree 
     WHERE 
     productID NOT IN (1040,1050,1168) 
     GROUP BY 
     productID 
    ) 
    AS [ok_products] 
     ON [ok_products].productID = [tree].productID 
WHERE 
    [tree].productID IN (1040,1050,1168) 
    AND [ok_products].productID IS NULL 
GROUP BY 
    [tree].productID 


SELECT 
    [tree].productID 
FROM 
    products_tree AS [tree] 
GROUP BY 
    [tree].productID 
HAVING 
     MAX(CASE WHEN [tree].productID  IN (1040,1050,1168) THEN 1 ELSE 0 END) = 1 
    AND MAX(CASE WHEN [tree].productID NOT IN (1040,1050,1168) THEN 1 ELSE 0 END) = 0 

Il y a d'autres, et les variations de chacun, mais cela devrait vous donner un très bon départ.Mais je voudrais vraiment souligner l'utilisation de GROUP BY et la contrepartie :) INDICES

+0

ProductID et CategoryID sont primaires sur leurs tables respectives, et sont INDEX sur la table des liens. J'ai changé DISTINCT en GROUP BY et j'ai obtenu la même performance. Je suppose que l'optimiseur remarquait cela. Le SQL que vous avez suggéré académiquement intéressant, j'ai accepté cette réponse. – rwired

+0

L'une des suggestions a-t-elle donné l'exemple que vous avez donné? Mon attente est que (dans MSSQLServer) en utilisant "NOT EXISTS" dans la clause where devrait être le plus rapide. (Utiliser 'segmentation' pour améliorer l'efficacité de la requête.) – MatBailie

0

Je crois que votre requête est assez bonne, mais vous pouvez le comparer avec les jointures:

 
SELECT DISTINCT pt1.productID 
FROM products_tree pt1 
LEFT JOIN products_tree pt2 ON pt2.productID = pt1.productID 
    AND pt2.categoryID pt1.categoryID 

WHERE pt1.categoryID IN (1040,1050,1168) 
    AND pt2.productID IS NULL 

Je ne sais pas si je pensais bien, mais je pense que vous comprenez mon approche. Je sélectionnerais directement productinfo si vous le souhaitez, alors les jointures auraient plus de sens (jointure interne des catégories que vous voulez, gauche rejoignant celles que vous ne voulez pas et vérifiant null)

0

Vous pouvez essayer un « NOT EXISTS » variante:

SELECT 
    pt.productID 
FROM 
    products_tree pt 
WHERE 
    pt.categoryID IN (1040,1050,1168) 
    AND NOT EXISTS (
    SELECT 1 
    FROM products_tree 
    WHERE productID = pt.productID AND categoryID NOT IN (1040,1050,1168) 
) 
GROUP BY 
    pt.productID; 
Questions connexes