La mejor manera de escribir consultas SQL que comprueba una columna para valores no NULL o NULL

17

Tengo un SP con un parámetro que tiene NULL como valor predeterminado y luego quiero hacer una consulta como esta:

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

Lo WHEREanterior verifica tanto un valor no NULL como un valor NULL para @VersionId.

¿Sería mejor en términos de rendimiento utilizar una IFinstrucción y duplicar la consulta en una que busque NULL y otra NULL de esa manera? :

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

¿O el optimizador de consultas lo hace esencialmente igual?

ACTUALIZAR:

(Nota: estoy usando SQL Server)

(Y hasta donde yo sé, el uso a.VersionId = @VersionIdpara ambos casos no funcionará, ¿verdad?)

usuario2173353
fuente
2
Artículo relacionado: inhalación de parámetros, incrustación y las opciones
RECOMPILAR
Generalmente uso lo siguiente: ISNULL (a.VersionId, @VersionId) = @VersionId
628426

Respuestas:

36

Este patrón

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

puede ser reemplazado con

EXISTS (SELECT column INTERSECT SELECT @argument)

Esto le permitirá hacer coincidir un NULL con un NULL y permitirá que el motor use un índice de manera columneficiente. Para un excelente análisis en profundidad de esta técnica, lo remito al artículo del blog de Paul White:

Como hay dos argumentos en su caso particular, puede usar la misma técnica de emparejamiento con @Blah, de esa manera podrá reescribir la cláusula WHERE completa de manera más o menos concisa:

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

Esto funcionará rápido con un índice activado (a.Blah, a.VersionId).


¿O el optimizador de consultas lo hace esencialmente igual?

En este caso, si. En todas las versiones (al menos) desde SQL Server 2005 en adelante, el optimizador puede reconocer el patrón col = @var OR (@var IS NULL AND col IS NULL)y reemplazarlo con la IScomparación adecuada . Esto depende de la coincidencia interna de reescritura, por lo que puede haber casos más complejos en los que esto no siempre es confiable.

En las versiones de SQL Server de 2008 SP1 CU5 inclusive , también tiene la opción de utilizar la optimización de incrustación de parámetros a través de OPTION (RECOMPILE), donde el valor de tiempo de ejecución de cualquier parámetro o variable se incrusta en la consulta como un literal antes de la compilación.

Entonces, al menos en gran medida, en este caso la elección es una cuestión de estilo, aunque la INTERSECTconstrucción es innegablemente compacta y elegante.

Los siguientes ejemplos muestran el 'mismo' plan de ejecución para cada variación (literales versus referencias de variables excluidas):

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
fuente