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 column
eficiente. 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 IS
comparació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 INTERSECT
construcció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);