哪些查询更快?
不存在:
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。
答案 0 :(得分:642)
我始终默认为NOT EXISTS
。
目前执行计划可能相同,但如果将来更改任一列以允许NULL
s NOT IN
版本需要做更多工作(即使没有{{1}实际上存在于数据中)NULL
如果NOT IN
s 存在的语义不太可能是您想要的那些。
当NULL
或Products.ProductID
都不允许[Order Details].ProductID
时,NULL
将被视为与以下查询完全相同。
NOT IN
确切的计划可能会有所不同,但对于我的示例数据,我得到以下内容。
一个相当常见的误解似乎是相关的子查询与连接相比总是“坏”。它们当然可以强制嵌套循环计划(逐行评估子查询),但此计划包括反半连接逻辑运算符。反半连接不限于嵌套循环,但可以使用散列或合并(如本示例中所示)连接。
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
的正确语义是不返回任何结果。请参阅额外的反半连接和行计数假脱机以验证添加到计划中的内容。
如果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
表是空的)。它应该在哪种情况下。在我的样本数据计划中,这是通过添加另一个反半连接来实现的。
这种效果显示在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
时,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。
假设我们在数据库中有以下两个表,它们构成一对多的表关系。
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 |
比方说,我们希望让所有获得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列匹配。
让我们考虑选择所有年级不低于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