Вопрос
Какой из этих запросов выполняется быстрее?
НЕ СУЩЕСТВУЕТ:
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)
Точный план может отличаться, но для моего примера данных я получаю следующее.
Достаточно распространенным заблуждением является то, что коррелированные подзапросы всегда «плохи» по сравнению с соединениями.Они, конечно, могут быть такими, когда они принудительно используют план вложенных циклов (подзапрос оценивается построчно), но этот план включает логический оператор, запрещающий полусоединение.Анти-полусоединения не ограничиваются вложенными циклами, но также могут использовать хэш-соединения или соединения слиянием (как в этом примере).
/*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]
таблица пуста).В этом случае так и должно быть.В плане моего примера данных это реализовано путем добавления еще одного анти-полусоединения, как показано ниже.
Эффект от этого показан на сообщение в блоге, на которое уже ссылается Бакли.В приведенном примере количество логических операций чтения увеличивается примерно с 400 до 500 000.
Кроме того, тот факт, что один NULL
может уменьшить количество строк до нуля, что очень затрудняет оценку мощности.Если SQL Server предполагает, что это произойдет, но на самом деле не было никаких NULL
строк в данных, остальная часть плана выполнения может быть катастрофически хуже, если это всего лишь часть более крупного запроса, с неподходящими вложенными циклами, вызывающими, например, повторное выполнение дорогостоящего поддерева.
Это не единственный возможный план выполнения NOT IN
на NULL
-способный столбец однако. В этой статье показан еще один для запроса против AdventureWorks2008
база данных.
Для NOT IN
на NOT NULL
столбец или NOT EXISTS
против столбца с нулевым или ненулевым значением он дает следующий план.
Когда столбец изменится на NULL
-способен NOT IN
план теперь выглядит так
Он добавляет в план дополнительный внутренний оператор соединения.Этот аппарат объяснено здесь.Здесь есть все, чтобы преобразовать предыдущий поиск по одиночному коррелированному индексу. Sales.SalesOrderDetail.ProductID = <correlated_product_id>
до двух поисков на внешний ряд.Дополнительный включен WHERE Sales.SalesOrderDetail.ProductID IS NULL
.
Поскольку это происходит при антиполусоединении, если оно возвращает какие-либо строки, второй поиск не произойдет.Однако если Sales.SalesOrderDetail
не содержит никаких NULL
ProductID
s это удвоит количество необходимых операций поиска.
Другие советы
Также имейте в виду, что 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