Вопрос

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

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

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

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

Это было полезно?

Решение

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

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

Когда ни 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 ProductIds - не возвращать никаких результатов.Посмотрите дополнительную катушку анти-полусоединения и счетчика строк, чтобы убедиться, что это добавлено в план.

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) 

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

Both NULL

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

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

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

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

Not EXists

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

Not In - Null

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

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

Другие советы

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

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

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

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

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

Допустим, в целях иллюстрации в таблице есть 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.Следовательно, это! = Сравнение возвращает неизвестно.Таблица логической истины для и утверждает, что неизвестные и истинные неизвестны, неизвестно и неверно ложно.Нет никакой ценности, которая может быть и с неизвестным, чтобы получить истинный результат

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

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

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

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

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

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

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

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

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

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

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

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 в этом случае будет предпочтительнее.

Но, в зависимости от оптимизатора СУБД, ситуация не может быть другой.

Как пример того, когда 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
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top