不在,不存在
哪些查询更快?
不存在:
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])
查询执行计划说,他们都做同样的事情。 如果是这样,那么推荐的forms是什么?
这是基于NorthWind数据库。
[编辑]
刚刚发现这个有用的文章: http : //weblogs.sqlteam.com/mladenp/archive/2007/05/18/60210.aspx
我想我会坚持不存在。
我总是默认NOT EXISTS
。
执行计划目前可能是相同的,但如果任一列在将来被更改为允许NULL
则NOT IN
版本将需要做更多的工作(即使数据中实际上不存在NULL
),以及如果NULL
存在, NOT IN
,不可能是你想要的。
当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
,则正确的语义是不返回结果。 请参阅额外的反半连接和行计数后台来validation添加到计划中的这一点。
如果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
子查询根本没有返回任何结果(即[Order Details]
表是空的)。 在这种情况下,它应该。 在我的样本数据计划中,通过添加另一个反半连接来实现。
这一点的影响显示在Buckley已经联系的博客文章中 。 在这个例子中,逻辑读取的数量从大约400增加到500,000。
此外,单个NULL
可以将行数减less到零的事实使得基数估计非常困难。 如果SQL Server假定会发生这种情况,但实际上数据中没有NULL
行,那么执行计划的其余部分可能会更糟糕,如果这只是更大的查询的一部分,那么使用不恰当的嵌套循环会导致重复执行昂贵子树例如 。
但是,这不是在NULL
-able列上的NOT IN
唯一可能的执行计划。 本文展示了另一个针对AdventureWorks2008
数据库的查询。
对于NOT NULL
列中的NOT IN
或者对于可为空或不可空列的NOT EXISTS
,它给出以下计划。
当列更改为NULL
时, NOT IN
计划现在看起来像
它在计划中增加了一个额外的内部连接操作符。 这个装置在这里解释 。 将Sales.SalesOrderDetail.ProductID = <correlated_product_id>
上的单个相关索引查找转换为每个外部行的两个查找。 额外的一个是WHERE Sales.SalesOrderDetail.ProductID IS NULL
。
由于这是在反半联接之下,如果那个返回任何行,第二个seek不会发生。 但是,如果Sales.SalesOrderDetail
不包含任何NULL
ProductID
则会使所需的查找操作数增加一倍。
另外请注意,NOT IN不等同于NOT EXISTS。
这篇文章解释得非常好
http://sqlinthewild.co.za/index.php/2010/02/18/not-exists-vs-not-in/
当子查询返回一个null时,NOT IN将不匹配任何行。
其原因可以通过查看NOT IN操作实际意义的细节来find。
比方说,为了说明目的,表中有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'd来产生结果TRUE
因此,如果该子查询的任何一行返回NULL,则整个NOT IN运算符将计算为FALSE或NULL,并且不会返回任何logging
如果执行计划员说他们是一样的,他们是一样的。 使用哪一个会使你的意图更明显 – 在这种情况下,第二个。
其实,我相信这将是最快的:
SELECT ProductID, ProductName FROM Northwind..Products p outer join Northwind..[Order Details] od on p.ProductId = od.ProductId) WHERE od.ProductId is null
我有一个约有12万条logging的表,只需要select那些不存在(与一个varchar列匹配)在其他四个表中约1500,4000,400,200,200行。所有涉及表具有唯一索引在有关的Varchar
列上。
NOT IN
花费大约10分钟, NOT EXISTS
花了4秒。
我有一个recursion查询,可能有一些未被激活的部分,可能有助于10分钟,但另一种select4秒解释,至less对我来说, NOT EXISTS
是好得多,或至less, IN
和EXISTS
不完全相同在继续使用代码之前总是值得一试。
在你的具体例子中,它们是相同的,因为优化器已经找出了你正在做的事情在两个例子中都是一样的。 但有可能的是,在非平凡的例子中,优化器可能不会这样做,并且在这种情况下,有时有理由更喜欢另一个。
如果您要在外部select中testing多个行,则不要select“首选”。 可以在执行开始时计算NOT IN语句内的子查询,并可以根据外部select中的每个值检查临时表,而不是每次都重新运行子查询,这与NOT EXISTS语句所要求的一样。
如果子查询必须与外部select相关联,那么NOT EXISTS可能是优选的,因为优化器可能发现简化,从而防止创build任何临时表来执行相同的function。
我正在使用
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。
这取决于..
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
如果优化器说他们是相同的,那么考虑人为因素。 我更喜欢看不存在:)