Когда использовать EXCEPT вместо NOT EXISTS в Transact SQL?
Я только недавно узнал о существовании нового предложения "EXCEPT" в SQL Server (немного поздно, я знаю...) благодаря чтению кода, написанного коллегой. Это действительно поразило меня!
Но тогда у меня есть несколько вопросов относительно его использования: когда это рекомендуется использовать? Есть ли разница с точки зрения производительности между его использованием и сопоставленным запросом, использующим "И НЕ СУЩЕСТВУЕТ..."?
После прочтения статьи EXCEPT в BOL я подумал, что это просто сокращение для второго варианта, но был удивлен, когда переписал пару запросов, используя его (поэтому у них был синтаксис "И НЕ СУЩЕСТВУЕТ", гораздо более знакомый мне), а затем проверил планы выполнения - сюрприз! Версия EXCEPT имела более короткий план выполнения и выполнялась также быстрее. Это всегда так?
Итак, я хотел бы знать: каковы рекомендации по использованию этого мощного инструмента?
5 ответов
EXCEPT
лечит NULL
значения как совпадающие.
Этот запрос:
WITH q (value) AS
(
SELECT NULL
UNION ALL
SELECT 1
),
p (value) AS
(
SELECT NULL
UNION ALL
SELECT 2
)
SELECT *
FROM q
WHERE value NOT IN
(
SELECT value
FROM p
)
вернет пустой набор строк.
Этот запрос:
WITH q (value) AS
(
SELECT NULL
UNION ALL
SELECT 1
),
p (value) AS
(
SELECT NULL
UNION ALL
SELECT 2
)
SELECT *
FROM q
WHERE NOT EXISTS
(
SELECT NULL
FROM p
WHERE p.value = q.value
)
вернусь
NULL
1
, и этот:
WITH q (value) AS
(
SELECT NULL
UNION ALL
SELECT 1
),
p (value) AS
(
SELECT NULL
UNION ALL
SELECT 2
)
SELECT *
FROM q
EXCEPT
SELECT *
FROM p
вернусь:
1
Рекурсивная ссылка также разрешена в EXCEPT
оговорка в рекурсивном CTE
хотя он ведет себя странным образом: он возвращает все, кроме последней строки предыдущего набора, а не все, кроме всего предыдущего набора:
WITH q (value) AS
(
SELECT 1
UNION ALL
SELECT 2
UNION ALL
SELECT 3
),
rec (value) AS
(
SELECT value
FROM q
UNION ALL
SELECT *
FROM (
SELECT value
FROM q
EXCEPT
SELECT value
FROM rec
) q2
)
SELECT TOP 10 *
FROM rec
---
1
2
3
-- original set
1
2
-- everything except the last row of the previous set, that is 3
1
3
-- everything except the last row of the previous set, that is 2
1
2
-- everything except the last row of the previous set, that is 3, etc.
1
SQL Server
разработчики, должно быть, просто забыли запретить это.
Я сделал много анализа, кроме, не существует, не в и оставил внешнее соединение. Обычно левое внешнее соединение является самым быстрым для поиска пропущенных строк, особенно для соединения по первичному ключу. Not In может быть очень быстрым, если вы знаете, что это будет небольшой список, возвращаемый в select.
Я использую EXCEPT много, чтобы сравнить то, что возвращается при переписывании кода. Запустите старый код, сохранив результаты. Запустите новый код, сохраняющий результаты, а затем используйте его, чтобы зафиксировать все различия. Это очень быстрый и простой способ найти различия, особенно когда нужно получить все различия, включая ноль. Очень хорошо для легкого кодирования на лету.
Но каждая ситуация отличается. Я говорю каждому разработчику, которого я когда-либо обучал. Попытайся. Делайте тайминги по-разному. Попробуй, пора, сделай.
EXCEPT сравнивает все (парные) столбцы двух полных выборов. NOT EXISTS сравнивает две или более таблиц в соответствии с условиями, указанными в предложении WHERE в подзапросе после ключевого слова NOT EXISTS.
EXCEPT может быть переписан с использованием NOT EXISTS. (КРОМЕ ВСЕХ можно переписать, используя ROW_NUMBER и NOT EXISTS.)
Получил это отсюда
Если ваш запрос точно настроен, то нет никакой разницы в производительности по ч / б с использованием предложения EXCEPT и NOT EXIST/NOT IN.. в первый раз, когда я запустил EXCEPT после изменения моего коррелированного запроса на него.. Я был удивлен, потому что он вернулся с результат только через 7 секунд, в то время как коррелированный запрос возвращался через 22 секунды.. затем я использовал отдельное предложение в своем коррелированном запросе и перезапустил.. он также вернулся через 7 секунд.. так что ИСКЛЮЧИТЬ хорошо, когда вы не знаете или не знаете успейте точно настроить ваш запрос, в противном случае производительность будет одинаковой.
Не учитывается план выполнения SQL-сервера. Когда я сталкивался с проблемами производительности, я всегда обнаруживал, что это было совершенно произвольно (с точки зрения пользователя, я уверен, что авторы алгоритмов поймут, почему), когда один синтаксис создает лучший план выполнения, а не другой.
В этом случае что-то в сравнении параметров запроса позволяет SQL-серверу найти ярлык, который он не мог сделать из оператора прямого выбора. Я уверен, что это недостаток в алгоритме. Другими словами, вы можете логически интерполировать одно и то же, но алгоритм не выполняет этот перевод для существующего запроса. Иногда это происходит из-за того, что алгоритм, который может надежно его выяснить, может занять больше времени, чем сам запрос, или, по крайней мере, разработчик алгоритма так думал.