不存在而不是存在

时间:2008-10-06 02:19:05

标签: 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)

或不是IN:

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

我想我会坚持使用NOT EXISTS。

11 个答案:

答案 0 :(得分:642)

我始终默认为NOT EXISTS

目前执行计划可能相同,但如果将来更改任一列以允许NULL s NOT IN版本需要做更多工作(即使没有{{1}实际上存在于数据中)NULL如果NOT IN s 存在的语义不太可能是您想要的那些。

NULLProducts.ProductID都不允许[Order Details].ProductID时,NULL将被视为与以下查询完全相同。

NOT IN

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

Neither NULL

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

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

如果/*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].ProductID,那么查询就会变为

NULL

这样做的原因是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) 包含任何[Order Details] NULL的正确语义是不返回任何结果。请参阅额外的反半连接和行计数假脱机以验证添加到计划中的内容。

One NULL

如果ProductId也更改为Products.ProductID,那么查询就会变为

NULL

原因之一是因为如果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表是空的)。它应该在哪种情况下。在我的样本数据计划中,这是通过添加另一个反半连接来实现的。

Both NULL

这种效果显示在the blog post already linked by Buckley中。在该示例中,逻辑读取的数量从大约400增加到500,000。

此外,单个[Order Details]可以将行数减少到零的事实使得基数估计非常困难。如果SQL Server假定会发生这种情况,但实际上数据中没有NULL行,那么执行计划的其余部分可能会更糟糕,如果这只是更大查询的一部分with inappropriate nested loops causing repeated execution of an expensive sub tree for example

但这不是NULL能力列上NOT IN唯一可行的执行计划。 This article shows another one用于查询NULL数据库。

对于AdventureWorks2008列上的NOT IN或针对可空或不可空列的NOT NULL,它会提供以下计划。

Not EXists

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

Not In - Null

它为计划添加了一个额外的内连接运算符。该装置是explained here。只需将NOT IN上的先前单个相关索引搜索转换为每个外行两个搜索。另外一个是Sales.SalesOrderDetail.ProductID = <correlated_product_id>

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

答案 1 :(得分:74)

还要注意,当涉及到null时,NOT IN不等同于NOT EXISTS。

这篇文章很好地解释了

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

  

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

     

通过查看详细信息可以找到原因   NOT IN operation实际上意味着。

     

让我们说,为了说明的目的,有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,并且不会记录任何记录   返回

答案 2 :(得分:22)

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

答案 3 :(得分:15)

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

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

答案 4 :(得分:10)

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

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

我有一个递归查询,可能有一些未调整的部分可能有助于10分钟,但另一个选项花了4秒解释,至少对我而言NOT EXISTS要好得多或至少IN EXISTS 1}}和{{1}}并不完全相同,在开始使用代码之前总是值得检查。

答案 5 :(得分:7)

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

如果您在外部选择中测试多行,则应首选

NOT IN。可以在执行开始时评估NOT IN语句中的子查询,并且可以针对外部选择中的每个值检查临时表,而不是每次都重新运行子选择,如同NOT EXISTS声明。

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

答案 6 :(得分:5)

我正在使用

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。

答案 7 :(得分:4)

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

就效率而言,我发现左连接为空语句更有效(当选择丰富的行时)

答案 8 :(得分:2)

这是一个很好的问题,所以我决定在自己的博客上写关于这个主题的a very detailed article

数据库表模型

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

SQL EXISTS tables

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

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

为了过滤在数学中成绩为10的student记录,我们可以使用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运算符的好处是,只要找到匹配的记录,就可以停止内部子查询的执行。

答案 9 :(得分:1)

如果优化器说它们是相同的,那么考虑人为因素。我更愿意看到NOT EXISTS:)

答案 10 :(得分:-2)

取决于..

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