为什么在T-SQL中存在EXCEPT?

时间:2012-02-02 04:02:41

标签: tsql

我刚刚在MSDN Library中阅读有关EXCEPT和INTERSECT的内容,并且遇到了如何使用INTERSECT的示例:

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

也许我是老式的,但我通常会使用以下代码来实现相同的结果:

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

我错过了什么,或者是和INNER JOIN一样的INTERSECT?使用一个优于另一个是否有性能优势?

同样的问题除外。这是怎么回事:

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

与此不同:

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

4 个答案:

答案 0 :(得分:25)

我将专注于EXCEPT因为我对它更熟悉。此外,作为免责声明,我的例子将在Sqlite中,因为我在Linux机器上。但是,Sqlite和SQL Server都应支持该功能。

INTERSECTEXCEPT都是设置运算符,源于relational algebra中的基本思想。它们以不同的值运行,是设置运算符。

你的例子很简单。我将使用Northwind示例数据库的Sqlite版本给出一个反例。

假设您想要获得所有订购员工ID为5的客户的客户ID,而不是那些同时订购员工ID为6的客户。这很简单自然,EXCEPT

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

这会在我的Northwind版本上返回14行。

假设您决定使用JOIN重写此内容。也许是这样的?

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

哎呀,525行。也许添加一个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

现在有28行,比我们EXCEPT得到的还要多。原因是这不是删除已经用6发出订单的CustomerID。相反,它返回所有订单为5的客户ID和某些除6以外的EmployeeID ,无论他们是否也有使用EmployeeID 6的订单。

简而言之,EXCEPTINTERSECT是设置运算符,用于比较两个查询,返回唯一元组,并且当然可以使用它们。

答案 1 :(得分:22)

  • INTERSECT和EXCEPT是半连接
  • JOIN是equi-join

所以当你加入2个匹配的表时,比方说,5行和3行

  • JOIN提供15行
  • INTERSECT提供3行

出于同样的原因,EXCEPT类似于OUTER JOIN

虽然我们关于半连接,但主要是

  • INTERSECT提供与EXISTS相同的结果
  • EXCEPT提供与NOT EXISTS
  • 相同的结果

“大部分”都是因为INTERSECT和EXCEPT

编辑,快速演示所有这些

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);

更新:2013年2月。添加了额外的列来描述操作

答案 2 :(得分:1)

您的“等效”查询示例有误 - 使用INTERSECT的查询并不总是返回与INNER JOIN相同的结果,EXCEPTLEFT JOIN的结果相同。

看看有关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更像(但不一样)与IN子句:

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

EXISTS

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

您可以适应EXCEPT条款的相同示例。

答案 3 :(得分:0)

在我看来,EXCEPTINTERSECT用于执行与JOIN命令相同的操作,但对于没有主键的表更简单,例如:

INTERSECT

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

要获得与JOIN相同的结果,您必须执行以下操作:

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 

更多信息here