585

这些查询中哪个更快?

不存在:

SELECT ProductID, ProductName 
FROM Northwind..Products p
WHERE NOT EXISTS (
    SELECT 1 
    FROM Northwind..[Order Details] od 
    WHERE p.ProductId = od.ProductId)

或不在:

SELECT ProductID, ProductName 
FROM Northwind..Products p
WHERE p.ProductID NOT IN (
    SELECT ProductID 
    FROM Northwind..[Order Details])

查询执行计划说他们都做同样的事情。如果是这样,推荐的形式是什么?

这是基于 NorthWind 数据库的。

[编辑]

刚刚发现这篇有用的文章: http ://weblogs.sqlteam.com/mladenp/archive/2007/05/18/60210.aspx

我想我会坚持不存在。

4

11 回答 11

746

我总是默认为NOT EXISTS.

目前执行计划可能是相同的,但是如果将来更改任一列以允许NULLsNOT IN版本将需要做更多的工作(即使NULL数据中实际上不存在 s )以及NOT INif NULLs语义存在无论如何都不太可能是你想要的。

当 noneProducts.ProductID[Order Details].ProductIDallow时NULLNOT IN将被视为与以下查询相同。

SELECT ProductID,
       ProductName
FROM   Products p
WHERE  NOT EXISTS (SELECT *
                   FROM   [Order Details] od
                   WHERE  p.ProductId = od.ProductId) 

确切的计划可能会有所不同,但对于我的示例数据,我得到以下信息。

都不是 NULL

一个相当普遍的误解似乎是,与连接相比,相关子查询总是“坏”的。当他们强制执行嵌套循环计划(逐行评估子查询)时,它们当然可以,但该计划包括反半连接逻辑运算符。反半连接不限于嵌套循环,也可以使用散列或合并(如本例所示)连接。

/*Not valid syntax but better reflects the plan*/ 
SELECT p.ProductID,
       p.ProductName
FROM   Products p
       LEFT ANTI SEMI JOIN [Order Details] od
         ON p.ProductId = od.ProductId 

如果[Order Details].ProductIDis NULL-able 则查询变为

SELECT ProductID,
       ProductName
FROM   Products p
WHERE  NOT EXISTS (SELECT *
                   FROM   [Order Details] od
                   WHERE  p.ProductId = od.ProductId)
       AND NOT EXISTS (SELECT *
                       FROM   [Order Details]
                       WHERE  ProductId IS NULL) 

这样做的原因是正确的语义 if [Order Details]contains any NULL ProductIds 是不返回任何结果。请参阅额外的反半联接和行计数假脱机以验证已添加到计划中。

一空

如果Products.ProductID也更改为NULL-able 查询则变为

SELECT ProductID,
       ProductName
FROM   Products p
WHERE  NOT EXISTS (SELECT *
                   FROM   [Order Details] od
                   WHERE  p.ProductId = od.ProductId)
       AND NOT EXISTS (SELECT *
                       FROM   [Order Details]
                       WHERE  ProductId IS NULL)
       AND NOT EXISTS (SELECT *
                       FROM   (SELECT TOP 1 *
                               FROM   [Order Details]) S
                       WHERE  p.ProductID IS NULL) 

这样做的原因是,除非子查询根本不返回任何结果(即表为空) ,否则NULL Products.ProductId不应在结果中返回a。在这种情况下应该。在我的示例数据计划中,这是通过添加另一个反半连接来实现的,如下所示。NOT IN[Order Details]

两者都为空

Buckley 已经链接的博客文章中显示了这种效果。在该示例中,逻辑读取的数量从大约 400 增加到 500,000。

此外,单个NULL可以将行数减少到零的事实使得基数估计非常困难。如果 SQL Server 假设这会发生,但实际上NULL数据中没有行,那么执行计划的其余部分可能会更糟,如果这只是较大查询的一部分,不适当的嵌套循环会导致重复执行昂贵的子以树为例

NOT IN然而,这并不是 a on a NULL-able 列唯一可能的执行计划。本文展示了另一个针对AdventureWorks2008数据库的查询。

对于NOT INonNOT NULL列或NOT EXISTS针对可为 null 或不可为 null 的列,它提供以下计划。

不存在

当列更改为NULL-able 时,NOT IN计划现在看起来像

不在 - 空

它在计划中添加了一个额外的内部连接运算符。这个装置在这里解释。所有这些都可以将先前的单个相关索引搜索转换为Sales.SalesOrderDetail.ProductID = <correlated_product_id>每外行两次搜索。额外的一个是 on WHERE Sales.SalesOrderDetail.ProductID IS NULL

由于这是一个反半连接,如果该连接返回任何行,则不会发生第二次搜索。但是,如果Sales.SalesOrderDetail不包含任何NULL ProductIDs ,它将使所需的查找操作数加倍。

于 2012-06-17T20:10:17.887 回答
95

另请注意,当涉及到 null 时,NOT IN 不等于 NOT EXISTS。

这个帖子解释的很好

http://sqlinthewild.co.za/index.php/2010/02/18/not-exists-vs-not-in/

当子查询返回一个空值时,NOT IN 将不匹配任何行。

可以通过查看 NOT IN 操作实际含义的详细信息来找到其原因。

假设为了说明的目的,表中有 4 行称为 t,有一个称为 ID 的列,其值为 1..4

WHERE SomeValue NOT IN (SELECT AVal FROM t)

相当于

WHERE SomeValue != (SELECT AVal FROM t WHERE ID=1)
AND SomeValue != (SELECT AVal FROM t WHERE ID=2)
AND SomeValue != (SELECT AVal FROM t WHERE ID=3)
AND SomeValue != (SELECT AVal FROM t WHERE ID=4)

让我们进一步说 AVal 为 NULL,其中 ID = 4。因此 != 比较返回 UNKNOWN。AND 的逻辑真值表表明 UNKNOWN 和 TRUE 是 UNKNOWN,UNKNOWN 和 FALSE 是 FALSE。没有任何值可以与 UNKNOWN 进行 AND 运算以产生结果 TRUE

因此,如果该子查询的任何行返回 NULL,则整个 NOT IN 运算符将评估为 FALSE 或 NULL,并且不会返回任何记录

于 2012-05-09T12:23:38.247 回答
25

如果执行计划者说它们是相同的,那么它们是相同的。使用任何一个会让你的意图更明显——在这种情况下,是第二个。

于 2008-10-06T02:21:46.137 回答
18

实际上,我相信这将是最快的:

SELECT ProductID, ProductName 
    FROM Northwind..Products p  
          outer join Northwind..[Order Details] od on p.ProductId = od.ProductId)
WHERE od.ProductId is null
于 2008-10-06T02:40:33.120 回答
12

我有一个包含大约 120,000 条记录的表,只需要选择其他四个表中不存在的那些(与 varchar 列匹配),行数约为 1500、4000、40000、200。所有涉及的表都有唯一索引在相关Varchar栏目上。

NOT IN花了大约10分钟,NOT EXISTS花了4秒。

我有一个递归查询,它可能有一些未调整的部分,这可能有助于 10 分钟,但另一个选项需要 4 秒解释,至少对我来说,这NOT EXISTS要好得多,或者至少是这样IN,并且EXISTS不完全相同并且总是值得一试在继续代码之前检查。

于 2014-07-07T17:12:53.127 回答
8

在您的具体示例中,它们是相同的,因为优化器已经确定您要尝试做的事情在两个示例中都是相同的。但有可能在非平凡的例子中优化器可能不会这样做,在这种情况下,有时有理由选择一个而不是另一个。

NOT IN如果您在外部选择中测试多行,则应该首选。可以在执行开始时评估语句中的子查询,并且可以根据外部选择中的每个值检查临时表,而不是像语句NOT IN所要求的那样每次都重新运行子选择。NOT EXISTS

如果子查询必须与外部选择相关联,那么NOT EXISTS可能更可取,因为优化器可能会发现一种简化,阻止创建任何临时表来执行相同的功能。

于 2008-10-06T02:54:46.930 回答
7

我正在使用

SELECT * from TABLE1 WHERE Col1 NOT IN (SELECT Col1 FROM TABLE2)

并发现它给出了错误的结果(错误的意思是没有结果)。因为 TABLE2.Col1 中有一个 NULL。

将查询更改为

SELECT * from TABLE1 T1 WHERE NOT EXISTS (SELECT Col1 FROM TABLE2 T2 WHERE T1.Col1 = T2.Col2)

给了我正确的结果。

从那时起,我开始在每个地方都使用 NOT EXISTS。

于 2013-06-13T15:02:31.620 回答
5

它们非常相似,但并不完全相同。

在效率方面,我发现left join is null语句更有效(当要选择大量行时)

于 2018-03-19T08:27:30.717 回答
5

数据库表模型

假设我们的数据库中有以下两个表,它们形成了一对多的表关系。

SQL EXISTS 表

student表是父表,而student_grade是子表,因为它有一个 student_id 外键列引用学生表中的 id 主键列。

包含以下student table两条记录:

| id | first_name | last_name | admission_score |
|----|------------|-----------|-----------------|
| 1  | Alice      | Smith     | 8.95            |
| 2  | Bob        | Johnson   | 8.75            |

并且,该student_grade表存储了学生获得的成绩:

| id | class_name | grade | student_id |
|----|------------|-------|------------|
| 1  | Math       | 10    | 1          |
| 2  | Math       | 9.5   | 1          |
| 3  | Math       | 9.75  | 1          |
| 4  | Science    | 9.5   | 1          |
| 5  | Science    | 9     | 1          |
| 6  | Science    | 9.25  | 1          |
| 7  | Math       | 8.5   | 2          |
| 8  | Math       | 9.5   | 2          |
| 9  | Math       | 9     | 2          |
| 10 | Science    | 10    | 2          |
| 11 | Science    | 9.4   | 2          |

SQL 存在

假设我们想让所有在数学课上获得 10 分的学生。

如果我们只对学生标识符感兴趣,那么我们可以运行如下查询:

SELECT
    student_grade.student_id
FROM
    student_grade
WHERE
    student_grade.grade = 10 AND
    student_grade.class_name = 'Math'
ORDER BY
    student_grade.student_id

但是,应用程序有兴趣显示 a 的全名student,而不仅仅是标识符,因此我们还需要student表中的信息。

为了过滤student数学中分数为 10 的记录,我们可以使用 EXISTS SQL 运算符,如下所示:

SELECT
    id, first_name, last_name
FROM
    student
WHERE EXISTS (
    SELECT 1
    FROM
        student_grade
    WHERE
        student_grade.student_id = student.id AND
        student_grade.grade = 10 AND
        student_grade.class_name = 'Math'
)
ORDER BY id

运行上面的查询时,我们可以看到只选择了 Alice 行:

| id | first_name | last_name |
|----|------------|-----------|
| 1  | Alice      | Smith     |

外部查询选择student我们有兴趣返回给客户端的行列。但是,WHERE 子句将 EXISTS 运算符与关联的内部子查询一起使用。

如果子查询返回至少一条记录,则 EXISTS 运算符返回 true,如果未选择任何行,则返回 false。数据库引擎不必完全运行子查询。如果匹配单个记录,则 EXISTS 运算符返回 true,并选择关联的其他查询行。

内部子查询是相关的,因为表的 student_id 列student_grade与外部学生表的 id 列匹配。

SQL 不存在

假设我们要选择所有成绩不低于 9 的学生。为此,我们可以使用 NOT EXISTS,它否定 EXISTS 运算符的逻辑。

因此,如果底层子查询没有返回记录,NOT EXISTS 运算符将返回 true。但是,如果单个记录被内部子查询匹配,NOT EXISTS 运算符将返回 false,并且可以停止子查询执行。

要将所有没有关联 student_grade 的学生记录与小于 9 的值匹配,我们可以运行以下 SQL 查询:

SELECT
    id, first_name, last_name
FROM
    student
WHERE NOT EXISTS (
    SELECT 1
    FROM
        student_grade
    WHERE
        student_grade.student_id = student.id AND
        student_grade.grade < 9
)
ORDER BY id

运行上面的查询时,我们可以看到只有 Alice 记录匹配:

| id | first_name | last_name |
|----|------------|-----------|
| 1  | Alice      | Smith     |

因此,使用 SQL EXISTS 和 NOT EXISTS 运算符的优点是,只要找到匹配的记录,就可以停止内部子查询的执行。

于 2020-01-28T10:27:43.583 回答
2

如果优化器说它们是相同的,那么请考虑人为因素。我更喜欢看不存在:)

于 2008-10-06T07:57:08.677 回答
-1

这取决于..

SELECT x.col
FROM big_table x
WHERE x.key IN( SELECT key FROM really_big_table );

不会相对较慢,限制查询检查的大小以查看它们是否在其中。 EXISTS 在这种情况下会更可取。

但是,根据 DBMS 的优化器,这可能没有什么不同。

作为 EXISTS 何时更好的示例

SELECT x.col
FROM big_table x
WHERE EXISTS( SELECT key FROM really_big_table WHERE key = x.key);
  AND id = very_limiting_criteria
于 2008-10-06T02:32:34.087 回答