Я всегда по умолчанию NOT EXISTS
.
Планы выполнения могут быть одинаковыми в данный момент, но если один столбец изменен в будущем, чтобы позволить NULL
S версии NOT IN
нужно будет делать больше работы (даже если не NULL
s не фактически присутствуют в данных) и семантика NOT IN
, если NULL
s настоящее время вряд ли будут теми, кого вы хотите в любом случае.
Если ни Products.ProductID
, ни [Order Details].ProductID
не разрешены NULL
s, то 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
-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)
Причина этого заключается в том, что правильная семантике если [Order Details]
содержит любой NULL
ProductId
s является не возвращать никаких результатов. См. Дополнительную шкалу антисоединения и счетчика строк, чтобы проверить это, добавленное к плану.

Если 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)
Причина этого одна объясняется тем, что NULL
Products.ProductId
не должны быть возвращены в результатах кроме если дополнительный запрос NOT IN
не должен был возвращать никаких результатов (т. е. таблица [Order Details]
пуста). В этом случае он должен. В плане моих выборочных данных это реализовано путем добавления другого анти-полу-соединения, как показано ниже.

Эффект этого показан на the blog post already linked by Buckley. В этом примере число логических чтений увеличивается с 400 до 500 000.
Кроме того, тот факт, что один NULL
может уменьшить количество строк до нуля, делает оценку мощности очень сложной. Если SQL Server предполагает, что это произойдет, но на самом деле в данных не было NULL
строк, остальная часть плана выполнения может быть катастрофически хуже, если это всего лишь часть более крупного запроса, with inappropriate nested loops causing repeated execution of an expensive sub tree for example.
Это не единственный возможный план выполнения для NOT IN
на столбце NULL
. This article shows another one для запроса к базе данных AdventureWorks2008
.
Для столбца NOT IN
на столбце NOT NULL
или NOT EXISTS
в отношении столбца с нулевым или невалютным значением он дает следующий план.

Когда изменения столбцов в NULL
-able NOT IN
план теперь выглядит

Это добавляет дополнительный внутренний присоединиться к оператору плана. Это устройство составляет explained here. Все, что нужно, чтобы преобразовать предыдущий одиночный коррелированный индексный поиск на Sales.SalesOrderDetail.ProductID = <correlated_product_id>
на два запроса на внешнюю строку. Дополнительный номер: WHERE Sales.SalesOrderDetail.ProductID IS NULL
.
Поскольку это под антисоединением, если он возвращает любые строки, второй поиск не будет происходить. Однако, если Sales.SalesOrderDetail
не содержит NULL
ProductID
, он удвоит количество требуемых операций поиска.
Вы попробовали план с помощью левого соединения, где null? – Sebas 2012-06-17 20:37:15
Интересно, отличается ли Базы данных, но в моем последнем тесте против PostgreSQL этот запрос `NOT IN`:` SELECT "A". * FROM "A" WHERE "A". "Id" NOT IN (SELECT "B". " AID «FROM» B »WHERE« B ».« Uid »= 2)` почти в 30 раз быстрее, чем этот «NOT EXISTS»: «SELECT» A ». * FROM« A »WHERE (NOT (EXISTS (SELECT 1 FROM «B» WHERE «B». «User_id» = 2 AND «B». «Aid» = «A». «Id»))) ` – 2012-12-04 19:06:02
Возможный дубликат [В чем разница между NOT EXISTS vs. NOT IN vs . LEFT JOIN WHERE NULL?] (Http://stackoverflow.com/questions/2246772/whats-the-difference-between-not-exists-vs-not-in-vs-left-join-where-is-null) – rcdmk 2016-05-14 17:50:23