Pourquoi EXCEPT existe-t-il dans T-SQL?

Je lisais juste à propos de EXCEPT et INTERSECT dans la bibliothèque MSDN et je suis tombé sur cet exemple d'utilisation D'INTERSECT:

USE AdventureWorks2008R2 GO 
SELECT ProductID 
FROM Production.Product
INTERSECT
SELECT ProductID 
FROM Production.WorkOrder ;
--Result: 238 Rows (products that have work orders)

Peut-être que je suis démodé, mais j'utiliserais généralement le code suivant pour obtenir le même résultat:

SELECT P.ProductID 
FROM Production.Product P 
INNER JOIN Production.WorkOrder W ON W.ProductID = P.ProductID

Est-ce que je manque quelque chose, ou est-ce que INTERSECT est le même que INNER JOIN? Y a-t-il un avantage de performance à utiliser l'un sur l'autre?

Même question pour EXCEPT. Comment est-ce:

USE AdventureWorks2008R2;
GO
SELECT ProductID 
FROM Production.Product
EXCEPT
SELECT ProductID 
FROM Production.WorkOrder ;
--Result: 266 Rows (products without work orders)

Différent de ce:

SELECT P.ProductID 
FROM Production.Product P 
LEFT JOIN Production.WorkOrder W ON W.ProductID = P.ProductID
WHERE W.ProductID IS NULL

?

22
demandé sur fieldingmellish 2012-02-02 08:02:40

4 réponses

Je vais me concentrer sur EXCEPT juste parce que je suis plus familier avec elle. En outre, en tant que disclaimer, mes exemples seront dans Sqlite, puisque je suis sur une boîte Linux. Cependant, Sqlite et SQL Server doivent prendre en charge la fonctionnalité.

Les Deux INTERSECT et EXCEPT sont des opérateurs d'ensembles, issus des idées sous-jacentes dealgèbre relationnelle . Ils fonctionnent sur des valeurs distinctes , étant des opérateurs définis.

Votre exemple est simpliste. Je vais donner un contre-exemple, en utilisant un Sqlite version de l'exemple de base de données Northwind.

Disons que vous voulez obtenir les CustomerIDs de tous les clients qui ont passé une commande avec EmployeeID de 5, mais pas ceux qui ont également passé une commande avec EmployeeID de 6. C'est simple et naturel, avec un EXCEPT.

SELECT CustomerID FROM orders
WHERE EmployeeID = 5
EXCEPT
SELECT CustomerID FROM orders
WHERE EmployeeID = 6

Cela renvoie 14 lignes sur ma version de Northwind.

Supposons que vous décidiez de réécrire ceci en utilisant JOIN s. peut-être quelque chose comme ça?

SELECT o1.CustomerID
FROM orders o1 INNER JOIN orders o2 ON o1.CustomerID = o2.CustomerID
WHERE o1.EmployeeID = 5 AND o2.EmployeeID != 6

Oups, 525 lignes. Peut-être ajouter un DISTINCT?

SELECT DISTINCT o1.CustomerID
FROM orders o1 INNER JOIN orders o2 ON o1.CustomerID = o2.CustomerID
WHERE o1.EmployeeID = 5 AND o2.EmployeeID != 6

Maintenant c'est 28 lignes, encore beaucoup plus que ce que nous obtenions avec EXCEPT. La raison en est que cela ne supprime pas les CustomerIDs qui ont passé une commande avec 6. Au contraire, il renvoie Tous Les CustomerIDs qui ont une commande avec 5 et certains EmployeeID autres que 6, qu'ils aient ou non également une commande avec EmployeeID 6.

En bref, EXCEPT et INTERSECT sont des opérateurs set qui comparent deux requêtes, renvoyant des tuples uniques, et ont certainement leur utiliser.

24
répondu voithos 2012-02-02 05:07:31
  • INTERSECT et EXCEPT sont des semi-jointures
  • la jointure est equi-join

Donc, lorsque vous rejoignez 2 tables qui correspondent, disons, 5 lignes et 3 lignes

  • JOIN donne 15 lignes
  • L'intersection donne 3 lignes

EXCEPT est similaire à OUTER JOIN pour la même raison

Alors que nous sommes sur les semi-jointures, alors surtout

  • INTERSECT donne les mêmes résultats que EXISTS
  • EXCEPT donne les mêmes résultats que NOT EXISTS

Le "surtout" vient parce que les deux se croisent et sauf

Modifier, démo rapide de tout cela

DECLARE @t1 TABLE (t1col INT);
INSERT @t1 VALUES (1), (2), (2), (3), (3), (5), (5);

DECLARE @t2 TABLE (t2col INT);
INSERT @t2 VALUES (1), (2), (3), (4);

SELECT 'INNER JOIN', * FROM @t1 t1 JOIN @t2 t2 ON t1.t1col = t2.t2col -- same both ways

SELECT 't1 INTERSECT t2', * FROM @t1 INTERSECT SELECT 't1 INTERSECT t2', * FROM @t2;

SELECT 't2 INTERSECT t1', * FROM @t2 INTERSECT SELECT 't2 INTERSECT t1', * FROM @t1;

SELECT 't1 EXISTS t2', * FROM @t1 t1
WHERE EXISTS (SELECT * FROM @t2 t2 WHERE t1.t1col = t2.t2col);

SELECT 't2 EXISTS t1', * FROM @t2 t2
WHERE EXISTS (SELECT * FROM @t1 t1 WHERE t1.t1col = t2.t2col);

SELECT 't1 LEFT JOIN t2, IS NULL', * FROM @t1 t1 LEFT JOIN @t2 t2 ON t1.t1col = t2.t2col WHERE t2.t2col IS NULL
SELECT 't2 LEFT JOIN t1, IS NULL', * FROM @t2 t2 LEFT JOIN @t1 t1 ON t1.t1col = t2.t2col WHERE t1.t1col IS NULL

SELECT 't1 EXCEPT t2', * FROM @t1 EXCEPT SELECT 't1 EXCEPT t2', * FROM @t2;

SELECT 't2 EXCEPT t1', * FROM @t2 EXCEPT SELECT 't2 EXCEPT t1', * FROM @t1;

SELECT 't1 NOT EXISTS t2', * FROM @t1 t1
WHERE NOT EXISTS (SELECT * FROM @t2 t2 WHERE t1.t1col = t2.t2col);

SELECT 't2 NOT EXISTS t1', * FROM @t2 t2
WHERE NOT EXISTS (SELECT * FROM @t1 t1 WHERE t1.t1col = t2.t2col);

Mise À Jour: Février 2013. Ajout d'une colonne supplémentaire pour décrire l'opération

20
répondu gbn 2013-02-08 09:54:10

Vos exemples de requêtes "équivalentes" sont erronés - la requête avec INTERSECT ne renvoie pas toujours le même résultat que INNER JOIN et le même pour EXCEPT et LEFT JOIN.

Regardez un exemple particulier sur INTERSECT:

DECLARE @t TABLE(t INT NOT NULL)
DECLARE @x TABLE(x INT NOT NULL)

INSERT @t
VALUES (1), (2), (3)

INSERT @x VALUES(1), (1), (1)

SELECT t FROM @t
INTERSECT SELECT x FROM @x

SELECT t FROM @t
INNER JOIN @x ON x = t

INTERSECT est plus comme (, mais pas le même) comme IN clause:

SELECT t FROM @t
WHERE t IN (select x FROM @x)

Ou EXISTS

SELECT t FROM @t
WHERE EXISTS (select * FROM @x WHERE x = t)

Les mêmes exemples que vous pouvez adapter à EXCEPT clause.

1
répondu Oleg Dok 2012-02-02 09:18:52

À mon avis EXCEPT et INTERSECT sont utilisés pour faire les mêmes choses que la commande JOIN, mais c'est plus simple avec des tables qui n'ont pas de clés primaires, par exemple:

Avec INTERSECT:

SELECT FIRSTNAME,
       LASTNAME,
       ADDRESSLINE1,
       CITY,
       STATEPROVINCECODE,
       POSTALCODE
FROM   MANAGER 
EXCEPT
SELECT FIRSTNAME,
       LASTNAME,
       ADDRESSLINE1,
       CITY,
       STATEPROVINCECODE,
       POSTALCODE
FROM   CUSTOMER

Et pour avoir les mêmes résultats avec JOIN, Vous devez faire:

SELECT   M.FIRSTNAME,
         M.LASTNAME,
         M.ADDRESSLINE1,
         M.CITY,
         M.STATEPROVINCECODE,
         M.POSTALCODE
FROM     MANAGER M
WHERE    NOT EXISTS (SELECT *
                     FROM   CUSTOMER C
                     WHERE  M.FIRSTNAME = C.FIRSTNAME
                            AND M.LASTNAME = C.LASTNAME
                            AND M.ADDRESSLINE1 = C.ADDRESSLINE1
                            AND M.CITY = C.CITY
                            AND M.POSTALCODE = C.POSTALCODE)
GROUP BY M.FIRSTNAME,M.LASTNAME,M.ADDRESSLINE1,M.CITY,
         M.STATEPROVINCECODE,M.POSTALCODE 

Plus d'informations ici.

0
répondu Gustavo F 2012-02-02 04:12:46