不存在而不是存在

ili*_*rit 507 sql sql-server notin

哪个查询更快?

不存在:

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

或者不是:

SELECT ProductID, ProductName 
FROM Northwind..Products p
WHERE p.ProductID NOT IN (
    SELECT ProductID 
    FROM Northwind..[Order Details])
Run Code Online (Sandbox Code Playgroud)

查询执行计划表明他们都做同样的事情.如果是这种情况,这是推荐的形式?

这基于NorthWind数据库.

[编辑]

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

我想我会坚持使用NOT EXISTS.

Mar*_*ith 662

我总是默认NOT EXISTS.

该执行计划可以是相同的时刻,但如果任一列在未来改变,以允许NULLS上的NOT IN版本需要做更多的工作(即使没有NULLs为实际存在的数据)和语义NOT IN如果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) 
Run Code Online (Sandbox Code Playgroud)

具体计划可能会有所不同,但对于我的示例数据,我得到以下内容.

没有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 
Run Code Online (Sandbox Code Playgroud)

如果[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) 
Run Code Online (Sandbox Code Playgroud)

这样做的原因是,如果[Order Details]包含任何NULL ProductIds 的正确语义是不返回任何结果.请参阅额外的反半连接和行计数假脱机以验证添加到计划中的此情况.

一个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) 
Run Code Online (Sandbox Code Playgroud)

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

都是NULL

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

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

但是,这不是NOT INon NULL-able列的唯一可能的执行计划.本文展示了另一个针对AdventureWorks2008数据库的查询.

对于NOT IN上一NOT NULL列或NOT EXISTS反对任何一个可以为空或非空列它提供了以下方案.

不存在

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

不在 - 空

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

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

  • @xis这些是在SQL Sentry计划资源管理器中打开的执行计划.您还可以在SSMS中以图形方式查看执行计划. (5认同)
  • 请问您如何获得如图所示的分析图? (4认同)
  • @Mayur在SQL Server中不需要这个.http://stackoverflow.com/questions/1597442/subquery-using-exists-1-or-exists/6140367#6140367 (4认同)

buc*_*ley 78

还要注意,当涉及到null时,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)
Run Code Online (Sandbox Code Playgroud)

相当于

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)
Run Code Online (Sandbox Code Playgroud)

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

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


Joh*_*kin 23

如果执行计划员说他们是相同的,那么他们就是一样的.使用任何一个会使你的意图更明显 - 在这种情况下,第二个.

  • 执行计划程序时间可能相同,但执行结果可能不同,因此存在差异.如果数据集中包含NULL,则NOT IN将产生意外结果(请参阅buckley的答案).最好使用NOT EXISTS作为默认值. (3认同)

Jam*_*ran 14

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

SELECT ProductID, ProductName 
    FROM Northwind..Products p  
          outer join Northwind..[Order Details] od on p.ProductId = od.ProductId)
WHERE od.ProductId is null
Run Code Online (Sandbox Code Playgroud)

  • @HLGEM不同意.根据我的经验,LOJ的最佳情况是它们是相同的,SQL Server将LOJ转换为反半连接.在最糟糕的情况下,SQL Server LEFT加入所有内容并过滤掉NULL,之后效率会低得多.[本文底部的示例](http://bradsruminations.blogspot.co.uk/2011/10/t-sql-tuesday-023-flip-side-of-join.html) (6认同)
  • 当优化器正在完成它的工作时,可能不是最快的,但当它没有时肯定会更快. (2认同)
  • 他可能也简化了对这篇文章的查询 (2认同)

小智 10

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

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

我有一个递归查询,可能有一些未调整的部分,可能有10分钟的贡献,但另一个选项花了4秒解释,至少对我来说,这NOT EXISTS是更好或至少那个IN,EXISTS并不完全相同,总是值得一个在进行代码之前检查.


Vla*_*cea 8

数据库表模型

\n

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

\n

SQL 存在表

\n

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

\n

其中student table包含以下两条记录:

\n
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
ID录取分数
1爱丽丝史密斯8.95
2鲍勃约翰逊8.75
\n
\n

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

\n
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n \n\n\n
ID班级名称年级学生卡
1数学101
2数学9.51
3数学9.751
4科学9.51
5科学91
6科学9.251
7数学8.52
8数学9.52
9数学92
10科学102
11科学9.42
\n

SQL 存在

\n

假设\xe2\x80\x99s 表示我们想要获取数学课上获得 10 分的所有学生。

\n

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

\n
SELECT\n    student_grade.student_id\nFROM\n    student_grade\nWHERE\n    student_grade.grade = 10 AND\n    student_grade.class_name = \'Math\'\nORDER BY\n    student_grade.student_id\n
Run Code Online (Sandbox Code Playgroud)\n

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

\n

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

\n
SELECT\n    id, first_name, last_name\nFROM\n    student\nWHERE EXISTS (\n    SELECT 1\n    FROM\n        student_grade\n    WHERE\n        student_grade.student_id = student.id AND\n        student_grade.grade = 10 AND\n        student_grade.class_name = \'Math\'\n)\nORDER BY id\n
Run Code Online (Sandbox Code Playgroud)\n

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

\n
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
ID
1爱丽丝史密斯
\n
\n

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

\n

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

\n

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

\n

SQL 不存在

\n

让\xe2\x80\x99s 考虑我们要选择所有成绩不低于9的学生。为此,我们可以使用NOT EXISTS,它否定了EXISTS运算符的逻辑。

\n

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

\n

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

\n
SELECT\n    id, first_name, last_name\nFROM\n    student\nWHERE NOT EXISTS (\n    SELECT 1\n    FROM\n        student_grade\n    WHERE\n        student_grade.student_id = student.id AND\n        student_grade.grade < 9\n)\nORDER BY id\n
Run Code Online (Sandbox Code Playgroud)\n

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

\n
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
ID
1爱丽丝史密斯
\n
\n

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

\n


Jef*_*dge 7

在您的具体示例中它们是相同的,因为优化器已经弄清楚您尝试做的是两个示例中相同的内容.但有可能的是,在非平凡的例子中,优化器可能不会这样做,并且在这种情况下,有理由有时会优先选择其中一个.

NOT IN如果您在外部选择中测试多行,则应该首选.NOT IN可以在执行开始时评估语句中的子查询,并且可以针对外部选择中的每个值检查临时表,而不是每次重新运行子NOT EXISTS语句,如语句所需.

如果子查询必须与外部选择相关联,则NOT EXISTS可能是优选的,因为优化器可能发现一种简化,该简化阻止创建任何临时表以执行相同的功能.


rav*_*ker 5

我在用

SELECT * from TABLE1 WHERE Col1 NOT IN (SELECT Col1 FROM TABLE2)
Run Code Online (Sandbox Code Playgroud)

并发现它给出了错误的结果(错误是指没有结果)。由于TABLE2.Col1中为NULL。

将查询更改为

SELECT * from TABLE1 T1 WHERE NOT EXISTS (SELECT Col1 FROM TABLE2 T2 WHERE T1.Col1 = T2.Col2)
Run Code Online (Sandbox Code Playgroud)

给了我正确的结果。

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


Ong*_*lem 5

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

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