Najlepszy sposób na napisanie zapytania SQL, które sprawdza kolumnę pod kątem wartości innej niż NULL lub NULL

17

Mam SP z parametrem, który ma wartość NULL jako wartość domyślną, a następnie chcę wykonać takie zapytanie:

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

Na WHEREpowyższe kontrole zarówno dla wartości niezerowe i wartości null @VersionId.

Czy pod względem wydajności lepiej byłoby użyć IFinstrukcji i zduplikować zapytanie na takie, które wyszukuje wartości inne niż NULL, a inne takie jak 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

Czy optymalizator zapytań sprawia, że ​​jest on zasadniczo taki sam?

AKTUALIZACJA:

(Uwaga: używam programu SQL Server)

(I o ile wiem, używanie a.VersionId = @VersionIdw obu przypadkach nie będzie działać, prawda?)

użytkownik2173353
źródło
2
Artykuł pokrewny: Parametr Sniffing, Osadzanie i opcje
RECOMPILE
Ogólnie używam następujących: ISNULL (a.VersionId, @VersionId) = @VersionId
628426,

Odpowiedzi:

36

Ten wzór

column = @argument OR (@argument IS NULL AND column IS NULL)

można zastąpić

EXISTS (SELECT column INTERSECT SELECT @argument)

Umożliwi to dopasowanie wartości NULL do NULL i pozwoli silnikowi na columnefektywne wykorzystanie indeksu . Aby uzyskać doskonałą dogłębną analizę tej techniki, odsyłam do artykułu na blogu Paula White'a:

Ponieważ w twoim przypadku istnieją dwa argumenty, możesz użyć tej samej techniki dopasowywania @Blah- w ten sposób będziesz mógł napisać mniej więcej zwięźle całą klauzulę WHERE:

WHERE
  EXISTS (SELECT a.Blah, a.VersionId INTERSECT SELECT @Blah, @VersionId)

Będzie działać szybko z włączonym indeksem (a.Blah, a.VersionId).


Czy optymalizator zapytań sprawia, że ​​jest on zasadniczo taki sam?

W takim przypadku tak. We wszystkich wersjach (przynajmniej) począwszy od SQL Server 2005 optymalizator rozpoznaje wzorzeccol = @var OR (@var IS NULL AND col IS NULL) i zastąpić go odpowiednim ISporównaniem. Polega to na wewnętrznym dopasowaniu przepisywania, więc mogą być bardziej złożone przypadki, w których nie zawsze jest to niezawodne.

W wersjach SQL Server od 2008 SP1 CU5 włącznie , masz również opcję użycia Optymalizacji osadzania parametrów za pośrednictwem OPTION (RECOMPILE), gdzie wartość czasu wykonania dowolnego parametru lub zmiennej jest osadzona w zapytaniu jako literał przed kompilacją.

Tak więc, przynajmniej w dużym stopniu, w tym przypadku wybór jest kwestią stylu, choć INTERSECT konstrukcja jest niewątpliwie kompaktowa i elegancka.

Poniższe przykłady przedstawiają „ten sam” plan wykonania dla każdej odmiany (z wyłączeniem literałów i odniesień do zmiennych):

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);
Andriy M.
źródło