web-dev-qa-db-fra.com

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

J'ai un SP avec un paramètre qui a NULL comme valeur par défaut et ensuite je veux faire une requête comme ceci:

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

Le WHERE ci-dessus 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 instruction IF 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, en utilisant a.VersionId = @VersionId pour les deux cas ne fonctionnera pas, n'est-ce pas?)

17
user2173353

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 efficacement un index sur column. Pour une excellente analyse approfondie de cette technique, je vous renvoie à l'article du 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 l'intégralité de la clause WHERE de manière plus ou moins concise:

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

Cela fonctionnera rapidement avec un index sur (a.Blah, a.VersionId).


Ou l'optimiseur de requête rend-il 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 comparaison IS appropriée. Cela dépend de la correspondance de réécriture interne, il peut donc y avoir des cas plus complexes où cela n'est pas toujours fiable.

Dans les versions de SQL Server à partir de 2008 SP1 CU5 inclus , vous avez également la possibilité d'utiliser Parameter Embedding Optimization via OPTION (RECOMPILE), où la valeur d'exécution de tout paramètre ou variable est incorporé dans la requête en tant que 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 construction INTERSECT 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);
36
Andriy M