Ce modèle
column = @argument OR (@argument IS NULL AND column IS NULL)
peut être remplacé par
EXISTS (SELECT column INTERSECT SELECT @argument)
Cela vous permettra de faire correspondre un NULL avec un NULL et permettra au moteur d'utiliser column
efficacement un index . Pour une excellente analyse approfondie de cette technique, je vous renvoie à l'article de blog de Paul White:
Comme il y a deux arguments dans votre cas particulier, vous pouvez utiliser la même technique de correspondance avec @Blah
- de cette façon, vous pourrez réécrire la clause WHERE entière de manière plus ou moins concise:
WHERE
EXISTS (SELECT a.Blah, a.VersionId INTERSECT SELECT @Blah, @VersionId)
Cela fonctionnera rapidement avec un index activé (a.Blah, a.VersionId)
.
Ou l'optimiseur de requête le rend essentiellement le même?
Dans ce cas, oui. Dans toutes les versions (au moins) à partir de SQL Server 2005, l'optimiseur peut reconnaître le modèle col = @var OR (@var IS NULL AND col IS NULL)
et le remplacer par la IS
comparaison appropriée . Cela dépend de la correspondance de réécriture interne, il peut donc y avoir des cas plus complexes où ce n'est pas toujours fiable.
Dans les versions de SQL Server à partir de 2008 SP1 CU5 inclus , vous avez également la possibilité d'utiliser l' optimisation d'intégration des paramètres via OPTION (RECOMPILE)
, où la valeur d'exécution de tout paramètre ou variable est incorporée dans la requête sous la forme d'un littéral avant la compilation.
Ainsi, au moins dans une large mesure, dans ce cas, le choix est une question de style, bien que la INTERSECT
construction soit indéniablement compacte et élégante.
Les exemples suivants montrent le «même» plan d'exécution pour chaque variation (littéraux contre références de variables exclus):
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);