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
?
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.
- 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
- traitez NULL différemment: pour un exemple entièrement travaillé, voir ceci par Paul White alias SQL Kiwi
- Appliquer DISTINCT
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
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.
À 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.