我总是默认为不存在。
目前的执行计划可能是相同的,但如果将来任意一列被更改为允许空值,则NOT in版本将需要做更多的工作(即使数据中实际上没有空值),而且如果存在空值,则NOT in的语义不太可能是您想要的语义。
当两者都不是产品。ProductID或[订单详细信息]。该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
如果[订单详情]。ProductID是可空的,然后查询变成
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 ProductIds,正确的语义是不返回任何结果。查看额外的反半连接和行计数线轴,以验证添加到计划中的这一点。
如果产品。ProductID也被更改为可空查询
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乘积。ProductId不应该在结果中返回,除非not in子查询根本不返回结果(即[Order Details]表为空)。在这种情况下,应该如此。在我的示例数据的计划中,这是通过添加另一个反半连接来实现的,如下所示。
Buckley已经链接了一篇博文,其中显示了这种影响。在这个例子中,逻辑读取的数量从400增加到500,000。
此外,一个NULL可以将行数减少到零,这使得基数估计非常困难。如果SQL Server假设会发生这种情况,但实际上数据中没有NULL行,那么执行计划的其余部分可能会灾难性地更糟,如果这只是一个更大查询的一部分,例如,不适当的嵌套循环会导致昂贵的子树的重复执行。
然而,对于可空列上的not IN,这并不是唯一可行的执行计划。本文展示了另一个针对AdventureWorks2008数据库的查询。
对于NOT NULL列上的NOT IN或对可空或不可空列上的NOT EXISTS,它给出以下计划。
当列更改为可空时,NOT IN计划现在看起来像这样
它向计划中添加了一个额外的内部连接运算符。这里解释一下这个装置。这一切都是为了将之前Sales.SalesOrderDetail.ProductID = <correlated_product_id>上的单个相关索引查找转换为每个外部行两个查找。另外一个是WHERE Sales.SalesOrderDetail.ProductID is NULL。
由于这是在反半连接下,如果该连接返回任何行,则不会发生第二次寻道。然而,如果销售。SalesOrderDetail不包含任何NULL productid,它将使所需的查找操作数量增加一倍。