Лучший способ написать SQL-запрос, который проверяет столбец на ненулевое значение или NULL

17

У меня есть SP с параметром, который имеет значение NULL в качестве значения по умолчанию, а затем я хочу сделать запрос следующим образом:

SELECT ...
FROM ...
WHERE a.Blah = @Blah AND (a.VersionId = @VersionId OR (@VersionId IS NULL AND a.VersionId IS NULL));

В WHEREвышеуказанные проверки для обоих значение , отличное от NULL , и значение NULL для @VersionId.

Было бы лучше с точки зрения производительности вместо этого использовать IFоператор и дублировать запрос в один, который ищет не NULL, а другой для NULL, вот так? :

IF @VersionId IS NULL BEGIN
    SELECT ...
    FROM ...
    WHERE a.Blah = @Blah AND a.VersionId IS NULL;
ELSE BEGIN
    SELECT ...
    FROM ...
    WHERE a.Blah = @Blah AND a.VersionId = @VersionId;
END

Или оптимизатор запросов делает то же самое?

ОБНОВИТЬ:

(Примечание: я использую SQL Server)

(И насколько я знаю, использование a.VersionId = @VersionIdв обоих случаях не сработает, не так ли?)

user2173353
источник
Я обычно использую следующее: ISNULL (a.VersionId, @VersionId) = @VersionId
628426,

Ответы:

36

Этот шаблон

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);
Андрей М
источник