Этот шаблон
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);