【问题标题】:Why does EXCEPT exist in T-SQL?为什么 T-SQL 中存在 EXCEPT?
【发布时间】:2012-02-02 04:02:40
【问题描述】:

我刚刚在 MSDN 库中阅读了关于 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

是我遗漏了什么,还是 INTERSECT 与 INNER JOIN 相同?使用其中一个对性能有好处吗?

除了相同的问题。这是怎么回事:

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

?

【问题讨论】:

  • -1:这不是 INTERSECT 的工作原理。您的第一个示例不返回具有工作订单的产品;它返回与产品的工作订单 ID 具有相同 ID 的任何产品的 ID。

标签: tsql


【解决方案1】:

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

INTERSECT 和 EXCEPT 都是集合运算符,源于 relational algebra 中的基本思想。它们对不同的值进行操作,是集合操作符。

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

假设您想要获取所有使用 EmployeeID 为 5 下订单的客户的 CustomerID,但不是那些同时使用 EmployeeID 为 6 下订单的客户的 CustomerID。使用EXCEPT 这很简单自然。

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

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

假设您决定使用JOINs 重写此代码。也许是这样的?

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。相反,它返回 all 订单为 5 和 一些 EmployeeID 不是 6 的 CustomerID ,无论他们是否还有一个 EmployeeID 为 6 的订单。

简而言之,EXCEPT 和 INTERSECT 是集合运算符,它们比较两个查询,返回唯一的元组,当然有它们的用途。

【讨论】:

  • 您可以编写这样的代码 select o1.CustomerId from (select CustomerId from orders where EmployeeID = 5) as o1 left outer join (select CustomerID from orders where EmployeeID = 6) as o2 on o1.CustomerID = o2.CustomerID 其中 o2.CustomerID 为空
【解决方案2】:
  • INTERSECT 和 EXCEPT 是半联接
  • 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 月。添加了额外的列来描述操作

【讨论】:

  • @Mikael Eriksson:我错了,已修复。我之前没有足够好的演示脚本
  • 其实回头看,EXCEPT不是antijoin,不是半连接吗?
  • @voithos 是也不是! EXCEPT = 左反半连接,INTERSECT = 左半连接。去重(因此比较)来自半连接。反连接部分的区别是它们之间的区别(包括或排除匹配的行)
【解决方案3】:

您的“等效”查询示例是错误的 - 带有 INTERSECT 的查询并不总是返回与 INNER JOIN 相同的结果,而对于 EXCEPT 和 LEFT 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 子句。

【讨论】:

    【解决方案4】:

    在我看来,EXCEPT 和 INTERSECT 用于执行与 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。

    【讨论】:

      猜你喜欢
      • 2021-07-14
      • 2011-08-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-10-08
      • 2017-05-19
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多