A melhor maneira de escrever a Consulta SQL que verifica uma coluna em busca de valor não NULL ou NULL

17

Eu tenho um SP com um parâmetro que tem NULL como valor padrão e, em seguida, quero fazer uma consulta como esta:

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

O WHEREacima verifica tanto um valor não NULL como um valor NULL @VersionId.

Em termos de desempenho, seria melhor usar uma IFinstrução e duplicar a consulta em uma que procure por NULL e outra por NULL dessa maneira? :

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

Ou o otimizador de consulta torna essencialmente o mesmo?

ATUALIZAR:

(Nota: estou usando o SQL Server)

(E, tanto quanto eu sei, usar a.VersionId = @VersionIdpara ambos os casos não funcionará, funcionará?)

user2173353
fonte
Geralmente uso o seguinte: ISNULL (a.VersionId, @VersionId) = @VersionId
628426

Respostas:

36

Esse padrão

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

pode ser substituído por

EXISTS (SELECT column INTERSECT SELECT @argument)

Isso permitirá que você combine um NULL com um NULL e permitirá que o mecanismo use um índice com columneficiência. Para uma excelente análise detalhada dessa técnica, refiro-lhe o artigo do blog de Paul White:

Como existem dois argumentos no seu caso em particular, você pode usar a mesma técnica de correspondência com @Blah- dessa forma, você poderá reescrever a cláusula WHERE inteira de forma mais ou menos concisa:

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

Isso funcionará rapidamente com um índice ativado (a.Blah, a.VersionId).


Ou o otimizador de consulta torna essencialmente o mesmo?

Nesse caso sim. Em todas as versões (pelo menos) do SQL Server 2005 em diante, o otimizador pode reconhecer o padrão col = @var OR (@var IS NULL AND col IS NULL)e substituí-lo pela IScomparação adequada . Isso depende da correspondência de reescrita interna; portanto, pode haver casos mais complexos em que isso nem sempre é confiável.

Nas versões do SQL Server de 2008 SP1 CU5 inclusive , você também tem a opção de usar a Otimização de incorporação de parâmetros via OPTION (RECOMPILE), na qual o valor de tempo de execução de qualquer parâmetro ou variável é incorporado na consulta como um literal antes da compilação.

Portanto, pelo menos em grande parte, nesse caso, a escolha é uma questão de estilo, embora a INTERSECTconstrução seja inegavelmente compacta e elegante.

Os exemplos a seguir mostram o 'mesmo' plano de execução para cada variação (excluídas as referências literais versus as variáveis):

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
fonte