不存在vs不存在


538

这些查询中哪个更快?

不存在:

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

我想我会坚持不存在。


3
您是否尝试过使用null为null的左联接进行计划?
塞巴(Sebas)2012年

1
NOT IN和NOT EXISTS不相同。看看以下链接以了解它们之间的区别:weblogs.sqlteam.com/mladenp/archive/2007/05/18/60210.aspx
Ameya Gokhale

2
我不知道如果数据库是不同的,但在我的PostgreSQL对最新的标杆,这个NOT IN查询:SELECT "A".* FROM "A" WHERE "A"."id" NOT IN (SELECT "B"."Aid" FROM "B" WHERE "B"."Uid" = 2)几乎是30倍的速度,因为这NOT EXISTSSELECT "A".* FROM "A" WHERE (NOT (EXISTS (SELECT 1 FROM "B" WHERE "B"."user_id" = 2 AND "B"."Aid" = "A"."id")))
PHUONG阮


1
@rcdmk您检查问题的日期了吗?
ilitirit '16

Answers:


693

我始终默认为NOT EXISTS

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

如果没有Products.ProductID[Order Details].ProductID允许NULLS中的NOT 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].ProductIDNULL-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]包含any NULL ProductId,则不返回任何结果。请参阅额外的防半连接和行计数假脱机,以验证是否已将其添加到计划中。

一个NULL

如果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不应该在返回的结果只是如果NOT IN子查询是在所有返回任何结果(即[Order Details]表是空的)。在这种情况下应该。在我的样本数据计划中,这是通过添加另一个反半联接来实现的,如下所示。

均为NULL

Buckley已经链接的博客中可以看到这样的效果。在该示例中,逻辑读取的数量从大约400增加到500,000。

另外,单个NULL可以将行数减少到零的事实使基数估计非常困难。如果SQL Server假定会发生这种情况,但实际上NULL数据中没有行,则执行计划的其余部分可能会灾难性地恶化,如果这只是较大查询的一部分,且嵌套循环不当会导致重复执行昂贵的子程序例如树

这不是唯一可行的执行计划 NOT INNULL -able列上。本文显示了另一个针对AdventureWorks2008数据库。

为了 NOT IN on NOT NULL列或NOT EXISTS针对null列或non nullable列的,给出以下计划。

不存在

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

不在-空

它为计划添加了一个额外的内部联接运算符。该设备是这里说明。只需要将先前的单个相关索引搜索转换为Sales.SalesOrderDetail.ProductID = <correlated_product_id>每个外行两个搜索。另一个打开WHERE Sales.SalesOrderDetail.ProductID IS NULL

由于这是在反半联接下,如果该联接返回任何行,则不会发生第二次寻道。但是,如果Sales.SalesOrderDetail不包含任何,NULL ProductID它将使所需的查找操作次数增加一倍。


4
请问您如何获得所示的分析图?
xis 2014年

5
@xis这些是在SQL Sentry计划资源管理器中打开的执行计划。您还可以在SSMS中以图形方式查看执行计划。
马丁·史密斯

我对此表示赞赏是出于以下唯一原因:NOT EXISTS以我期望的方式NOT IN起作用(实际上没有)。
levininja 2015年

使用NOT EXISTS,我尝试使用SELECT 1,例如NOT EXISTS(从某个地方的SELECT 1到某处),这样数据库实际上就不需要从磁盘返回列。使用EXPLAIN确定这是否对您的情况有所帮助可能是个好主意。
Mayur Patel 2015年

4
@Mayur在SQL Server中无需这样做。stackoverflow.com/questions/1597442/...
马丁·史密斯

84

还要注意,当为空时,NOT IN不等于NOT EXISTS。

这篇文章很好地解释了

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

当子查询甚至返回一个null时,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进行与运算以产生结果TRUE

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


24

如果执行计划者说他们是相同的,那么他们是相同的。使用任何一种都会使您的意图更加明显-在这种情况下,使用第二种。


3
执行计划者时间可能相同,但是执行结果可能不同,因此有所不同。如果您的数据集中有NULL,则NOT IN会产生意外结果(请参见buckley的答案)。最好使用NOT EXISTS作为默认值。
nanonerd

15

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

SELECT ProductID, ProductName 
    FROM Northwind..Products p  
          outer join Northwind..[Order Details] od on p.ProductId = od.ProductId)
WHERE od.ProductId is null

2
当优化器完成任务时,它可能不是最快的,但是当优化器工作时,它肯定会更快。
卡德·鲁

2
他可能已经简化了查询这个岗位太
基普

1
同意左外部联接通常比子查询快。
HLGEM,

7
@HLGEM不同意。以我的经验,LOJ的最佳情况是它们是相同的,并且SQL Server将LOJ转换为反半联接。在最坏的情况下,SQL Server LEFT JOIN将所有内容都过滤掉,然后过滤掉NULL,效率可能低得多。本文底部的示例
Martin Smith

12

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

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

我有可能有一些不转动的部分可能已经到了10分钟,贡献了一个递归查询,但其他选项以4秒解释,至少对我来说,NOT EXISTS是远远至少是好还是INEXISTS不完全一样,总是值得在继续进行代码之前进行检查。


8

在您的特定示例中,它们是相同的,因为优化器已经确定您要尝试执行的操作在两个示例中都是相同的。但是有可能在非平凡的示例中,优化器可能不会这样做,并且在这种情况下,有时有理由偏爱一个。

NOT IN如果要在外部选择中测试多行,则应首选。NOT IN可以在执行开始时评估语句内的子查询,并且可以对照外部选择中的每个值检查临时表,而不必每次都使用子选择重新运行子选择。NOT EXISTS语句。

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


6

我在用

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。


5

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

在效率方面,我发现左联接为null语句更有效(当要选择大量行时)


2

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


1

这是一个很好的问题,所以我决定在我的博客上写一篇有关此主题的非常详细的文章

数据库表模型

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

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_grade表的student_id列与外部学生表的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运算符的好处是,只要找到匹配的记录,就可以停止内部子查询的执行。


-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

1
INEXISTS 在SQL Server中获得相同的计划。现在的问题是关于NOT INVS NOT EXISTS反正。
马丁·史密斯
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.