2008-10-06 4 views
406

Какой из этих запросов быстрее?NOT IN vs NOT EXISTS

НЕ СУЩЕСТВУЕТ:

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]) 

План выполнения запроса говорит, что они оба делают то же самое. Если это так, то какая рекомендуемая форма?

Это основано на базе данных NorthWind.

[Редактировать]

Просто найти эту полезную статью: http://weblogs.sqlteam.com/mladenp/archive/2007/05/18/60210.aspx

Я думаю, что я буду придерживаться НЕ СУЩЕСТВУЕТ.

+1

Вы попробовали план с помощью левого соединения, где null? – Sebas 2012-06-17 20:37:15

+1

Интересно, отличается ли Базы данных, но в моем последнем тесте против 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

+0

Возможный дубликат [В чем разница между 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

ответ

553

Я всегда по умолчанию 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) 

Точный план может отличаться, но для данных моего примера я получаю следующее.

Neither NULL

Разумно распространенное заблуждение, кажется, что коррелированные подзапросы всегда «плохо» по сравнению с соединениями. Они, конечно же, могут быть, когда они заставляют план вложенных циклов (подпроцесс оценивается по строкам), но этот план включает в себя антисемитированный логический оператор. Анти-полу-соединения не ограничены вложенными циклами, но могут использовать хэш или слияние (как в этом примере).

/*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] содержит любой NULLProductId s является не возвращать никаких результатов. См. Дополнительную шкалу антисоединения и счетчика строк, чтобы проверить это, добавленное к плану.

One NULL

Если 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) 

Причина этого одна объясняется тем, что NULLProducts.ProductId не должны быть возвращены в результатах кроме если дополнительный запрос NOT IN не должен был возвращать никаких результатов (т. е. таблица [Order Details] пуста). В этом случае он должен. В плане моих выборочных данных это реализовано путем добавления другого анти-полу-соединения, как показано ниже.

Both NULL

Эффект этого показан на 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 в отношении столбца с нулевым или невалютным значением он дает следующий план.

Not EXists

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

Not In - Null

Это добавляет дополнительный внутренний присоединиться к оператору плана. Это устройство составляет explained here. Все, что нужно, чтобы преобразовать предыдущий одиночный коррелированный индексный поиск на Sales.SalesOrderDetail.ProductID = <correlated_product_id> на два запроса на внешнюю строку. Дополнительный номер: WHERE Sales.SalesOrderDetail.ProductID IS NULL.

Поскольку это под антисоединением, если он возвращает любые строки, второй поиск не будет происходить. Однако, если Sales.SalesOrderDetail не содержит NULLProductID, он удвоит количество требуемых операций поиска.

22

Если планировщик выполнения говорит, что они такие же, они одинаковы. Используйте то, что сделает ваше намерение более очевидным - в этом случае второе.

0

Это зависит ..

SELECT x.col 
FROM big_table x 
WHERE x.key IN(SELECT key FROM really_big_table); 

не будет относительно медленным не так много, чтобы ограничить размер того, что проверка запроса, чтобы увидеть, если они ключ в. EXISTS будет предпочтительнее в этом случае.

Но, в зависимости от оптимизатора СУБД, это не может быть иначе.

Как пример, когда 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 
11

На самом деле, я считаю, что это будет самый быстрый:

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

В вашем конкретном примере они одинаковы, потому что оптимизатор понял то, что вы пытаетесь сделать, одинаково в обоих примерах. Но возможно, что в нетривиальных примерах оптимизатор может этого не делать, и в этом случае есть причины предпочесть друг другу по случаю.

NOT IN должно быть предпочтительным, если вы тестируете несколько строк во внешнем выборе. Подзапрос внутри оператора NOT IN может быть оценен в начале выполнения, а временная таблица может быть проверена на каждое значение во внешнем select, а не на повторное выполнение подзаголовка каждый раз, как это требуется в инструкции NOT EXISTS ,

Если подзапрос должен быть соотнесен с наружным выбором, затем NOT EXISTS может быть предпочтительными, так как оптимизатор может обнаружить упрощение, которое предотвращает создание каких-либо временных таблицы для выполнения той же функции.

0

Если оптимизатор говорит, что они одинаковы, рассмотрите человеческий фактор. Я предпочитаю видеть НЕ СУЩЕСТВУЮЩИЕ :)

62

Также имейте в виду, что NOT IN не эквивалентно NOT EXISTS, когда дело касается нуля.

Это сообщение объясняет это очень хорошо

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

Когда подзапрос возвращает даже один нуль, NOT IN не найдено ни одного строк.

Причины этого можно найти, посмотрев подробности того, что на самом деле означает операцияNOT IN.

Скажем, для целей иллюстрации, что существует 4 строки в таблице под названием т, есть столбец с именем 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. Логическая таблица истинности для состояний И , что UNKNOWN и TRUE UNKNOWN, UNKNOWN и FALSE FALSE. Там нет нет значения, которое может быть AND'd с неизвестным для получения результата ИСТИНА

Следовательно, если любая строка этого подзапроса возвращает значение NULL, то все НЕ В оператора будет оценивать либо FALSE или NULL, и никаких будут записей be

2

Я использовал

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) 

дал мне правильные результаты.

С тех пор я начал использовать НЕ СУЩЕСТВУЮЩИЙ каждый раз.

6

У меня есть таблица, насчитывающая около 120 000 записей, и вам нужно выбрать только те, которые не существуют (согласованы с колонкой varchar) в четырех других таблицах с количеством строк около 1500, 4000, 40000, 200. Все задействованные таблицы имеют уникальный индекс для соответствующего столбца Varchar.

NOT IN заняло 10 минут, NOT EXISTS заняло 4 секунды.

У меня есть рекурсивный запрос, который мог бы имел некоторые расстроенные сечение, которое, возможно, внесли свой вклад в 10 минут, но другой вариант с 4 сек объясняет, по крайней мере мне, что NOT EXISTS гораздо лучше, или, по крайней мере, IN и EXISTS не точно так же и всегда стоит проверить, прежде чем идти вперед с кодом.