НЕ В ПРОТИВ НЕ СУЩЕСТВУЕТ - PullRequest
480 голосов
/ 06 октября 2008

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

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

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

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

Ответы [ 10 ]

642 голосов
/ 18 июня 2012

Я всегда по умолчанию NOT EXISTS.

Планы выполнения могут быть такими же на данный момент, но если в будущем любой столбец будет изменен, чтобы разрешить NULL s, версия NOT IN должна будет выполнять больше работы (даже если на самом деле нет NULL s в данных), и семантика NOT IN, если NULL s присутствует , вряд ли будет той, которую вы хотите в любом случае.

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

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

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

One NULL

Если 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 вообще не должен давать результатов (то есть * 1051) * таблица пуста). В каком случае это должно быть. В плане для моих образцов данных это реализовано добавлением еще одного анти-полусоединения, как показано ниже.

Both NULL

Эффект этого показан в блоге, уже связанном с Бакли . В этом примере количество логических операций чтения увеличилось с 400 до 500 000.

Кроме того, тот факт, что один NULL может уменьшить количество строк до нуля, очень затрудняет оценку количества элементов. Если SQL Server предполагает, что это произойдет, но на самом деле в данных не было строк NULL, остальная часть плана выполнения может быть катастрофически хуже, если это только часть более крупного запроса, с неподходящими вложенными циклами, вызывающими повторное выполнение дорогого поддерева, например .

Однако это не единственный возможный план выполнения для NOT IN в столбце с поддержкой NULL. В этой статье показан еще один для запроса к базе данных AdventureWorks2008.

Для столбца NOT IN в столбце NOT NULL или NOT EXISTS для столбца, который может иметь значение NULL или NULL, он дает следующий план.

Not EXists

Когда столбец меняется на NULL, при этом план NOT IN теперь выглядит как

Not In - Null

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

Так как это находится под анти-полусоединением, если этот возвращает какие-либо строки, второй поиск не произойдет Однако, если Sales.SalesOrderDetail не содержит NULL ProductID с, это удвоит количество требуемых операций поиска.

74 голосов
/ 09 мая 2012

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

Этот пост очень хорошо объясняет

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

Когда подзапрос возвращает хотя бы один ноль, NOT IN не будет совпадать ни с одним строки.

Причину этого можно узнать, посмотрев на детали того, что НЕ В работе фактически означает.

Допустим, в целях иллюстрации, что в В таблице с именем 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. Следовательно, это! = сравнение возвращает НЕИЗВЕСТНО. Логическая таблица истинности для состояний AND что UNKNOWN и TRUE - НЕИЗВЕСТНО, UNKNOWN и FALSE - ЛОЖЬ. Есть нет значения, которое может быть AND'd с UNKNOWN для получения результата TRUE

Следовательно, если какая-либо строка этого подзапроса возвращает NULL, весь NOT IN Оператор оценивает в FALSE или NULL, и никакие записи не будут вернулся

22 голосов
/ 06 октября 2008

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

15 голосов
/ 06 октября 2008

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

SELECT ProductID, ProductName 
    FROM Northwind..Products p  
          outer join Northwind..[Order Details] od on p.ProductId = od.ProductId)
WHERE od.ProductId is null
10 голосов
/ 07 июля 2014

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

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

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

7 голосов
/ 06 октября 2008

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

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

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

5 голосов
/ 13 июня 2013

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

SELECT * from TABLE1 WHERE Col1 NOT IN (SELECT Col1 FROM TABLE2)

и обнаружил, что он дает неправильные результаты (Под неправильным я подразумеваю отсутствие результатов). Как был NULL в TABLE2.Col1.

При изменении запроса на

SELECT * from TABLE1 T1 WHERE NOT EXISTS (SELECT Col1 FROM TABLE2 T2 WHERE T1.Col1 = T2.Col2)

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

С тех пор я начал использовать NOT EXISTS каждый раз.

4 голосов
/ 19 марта 2018

Они очень похожи, но на самом деле не одинаковы.

С точки зрения эффективности, я обнаружил, что левое объединение является нулевым оператор более эффективен (когда нужно выбрать множество строк, то есть)

1 голос
/ 06 октября 2008

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

0 голосов
/ 06 октября 2008

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

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
...