Meilleure façon d'écrire une requête SQL qui vérifie une colonne pour une valeur non NULL ou NULL

17

J'ai un SP avec un paramètre qui a NULL comme valeur par défaut, puis je veux faire une requête comme celle-ci:

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

Ce qui WHEREprécède vérifie à la fois une valeur non NULL et une valeur NULL pour @VersionId.

Serait-il préférable en termes de performances d'utiliser à la place une IFinstruction et de dupliquer la requête en une qui recherche non NULL et une autre NULL comme ça? :

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 l'optimiseur de requête le rend essentiellement le même?

MISE À JOUR:

(Remarque: j'utilise SQL Server)

(Et pour autant que je sache, l'utilisation a.VersionId = @VersionIddans les deux cas ne fonctionnera pas, n'est-ce pas?)

user2173353
la source
J'utilise généralement les éléments suivants: ISNULL (a.VersionId, @VersionId) =
@VersionId

Réponses:

36

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 columnefficacement 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 IScomparaison 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 INTERSECTconstruction 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);
Andriy M
la source