Ця закономірність
column = @argument OR (@argument IS NULL AND column IS NULL)
можна замінити на
EXISTS (SELECT column INTERSECT SELECT @argument)
Це дозволить вам зіставити NULL з NULL і дозволить двигуну ефективно використовувати індекс column
. Для відмінного глибокого аналізу цієї методики я посилаюсь на статтю в блозі Пола Уайта:
Оскільки у вашому конкретному випадку є два аргументи, ви можете використовувати ту саму техніку відповідності @Blah
- таким чином ви зможете переписати весь пункт WHERE більш-менш стисло:
WHERE
EXISTS (SELECT a.Blah, a.VersionId INTERSECT SELECT @Blah, @VersionId)
Це буде працювати швидко з індексом на (a.Blah, a.VersionId)
.
Або оптимізатор запитів робить його по суті таким же?
У цьому випадку так. У всіх версіях (принаймні) від SQL Server 2005 і далі оптимізатор може розпізнати шаблонcol = @var OR (@var IS NULL AND col IS NULL)
і замінити його належним IS
порівнянням. Це покладається на внутрішнє узгодження переписування, тому можуть бути складніші випадки, коли це не завжди є надійним.
У версіях SQL Server від 2008 р. Включно з SP1 CU5 ви також можете скористатися оптимізацією вбудовування параметрів через OPTION (RECOMPILE)
, де значення часу виконання будь-якого параметра чи змінної вбудовується в запит як буквальне перед компіляцією.
Тож, принаймні значною мірою, вибір у цьому випадку є питанням стилю, хоча INTERSECT
конструкція є безперечно компактною та елегантною.
Наведені нижче приклади показують "той самий" план виконання для кожного варіанту (виключаються літерали проти змінних посилань):
DECLARE @T AS table
(
c1 integer NULL,
c2 integer NULL,
c3 integer NULL
UNIQUE CLUSTERED (c1, c2)
);
-- Some data
INSERT @T
(c1, c2, c3)
SELECT 1, 1, 1 UNION ALL
SELECT 2, 2, 2 UNION ALL
SELECT NULL, NULL, NULL UNION ALL
SELECT 3, 3, 3;
-- Filtering conditions
DECLARE
@c1 integer,
@c2 integer;
SELECT
@c1 = NULL,
@c2 = NULL;
-- Writing the NULL-handling out explicitly
SELECT *
FROM @T AS T
WHERE
(
T.c1 = @c1
OR (@c1 IS NULL AND T.c1 IS NULL)
)
AND
(
T.c2 = @c2
OR (@c2 IS NULL AND T.c2 IS NULL)
);
-- Using INTERSECT
SELECT *
FROM @T AS T
WHERE EXISTS
(
SELECT T.c1, T.c2
INTERSECT
SELECT @c1, @c2
);
-- Using separate queries
IF @c1 IS NULL AND @c2 IS NULL
SELECT *
FROM @T AS T
WHERE T.c1 IS NULL
AND T.c2 IS NULL
ELSE IF @c1 IS NULL
SELECT *
FROM @T AS T
WHERE T.c1 IS NULL
AND T.c2 = @c2
ELSE IF @c2 IS NULL
SELECT *
FROM @T AS T
WHERE T.c1 = @c1
AND T.c2 IS NULL
ELSE
SELECT *
FROM @T AS T
WHERE T.c1 = @c1
AND T.c2 = @c2;
-- Using OPTION (RECOMPILE)
-- Requires 2008 SP1 CU5 or later
SELECT *
FROM @T AS T
WHERE
(
T.c1 = @c1
OR (@c1 IS NULL AND T.c1 IS NULL)
)
AND
(
T.c2 = @c2
OR (@c2 IS NULL AND T.c2 IS NULL)
)
OPTION (RECOMPILE);