web-dev-qa-db-fra.com

Comment réécrire IS DISTINCT DE et IS PAS DISTINCT DE?

Comment réécrire des expressions contenant les opérateurs standard IS DISTINCT FROM et IS NOT DISTINCT FROM dans des implémentations SQL telles que Microsoft SQL Server 2008R2 qui ne les prennent pas en charge?

43
Jason Kresowaty

Le prédicat IS DISTINCT FROM a été introduit en tant que fonctionnalité T151 de SQL: 1999 et sa négation lisible, IS NOT DISTINCT FROM, a été ajoutée en tant que fonctionnalité T152 de SQL: 2003. Le but de ces prédicats est de garantir que le résultat de la comparaison de deux valeurs est soit True, soit False, jamais Inconnu.

Ces prédicats fonctionnent avec tout type comparable (y compris les lignes, les tableaux et les multisets), ce qui complique leur imitation exacte. Cependant, SQL Server ne supportant pas la plupart de ces types, nous pouvons aller assez loin en vérifiant les arguments/opérandes nuls:

  • a IS DISTINCT FROM b peut être réécrit comme:

    ((a <> b OR a IS NULL OR b IS NULL) AND NOT (a IS NULL AND b IS NULL))
    
  • a IS NOT DISTINCT FROM b peut être réécrit comme:

    (NOT (a <> b OR a IS NULL OR b IS NULL) OR (a IS NULL AND b IS NULL))
    

Votre propre réponse est incorrecte car elle ne tient pas compte du fait que FALSE OR NULL est évalué à inconnu. Par exemple, NULL IS DISTINCT FROM NULL doit être évalué à False. De même, 1 IS NOT DISTINCT FROM NULL doit être évalué à False. Dans les deux cas, vos expressions donnent Inconnu.

36
Chris Bandy

Une autre solution que j'aime utilise le résultat booléen à deux valeurs de EXISTS combiné à INTERSECT. Cette solution devrait fonctionner dans SQL Server 2005+.

  • a IS NOT DISTINCT FROM b peut être écrit comme:

    EXISTS(SELECT a INTERSECT SELECT b)

Comme indiqué, INTERSECT considère que deux valeurs NULL sont égales. Par conséquent, si les deux sont NULL, INTERSECT renvoie une seule ligne et EXISTS renvoie true.

  • a IS DISTINCT FROM b peut être écrit comme:

    NOT EXISTS(SELECT a INTERSECT SELECT b)

Cette approche est beaucoup plus concise si vous avez plusieurs colonnes nullables que vous devez comparer dans deux tableaux. Par exemple, pour renvoyer des lignes dans TableB ayant des valeurs différentes de Col1, Col2 ou Col3 par rapport à TableA, vous pouvez utiliser les éléments suivants:

SELECT *
FROM TableA A
   INNER JOIN TableB B ON A.PK = B.PK
WHERE NOT EXISTS(
   SELECT A.Col1, A.Col2, A.Col3
   INTERSECT
   SELECT B.Col1, B.Col2, B.Col3);

Paul White explique cette solution de manière plus détaillée: http://web.archive.org/web/20180422151947/http://sqlblog.com:80/blogs/paul_white/archive/2011/06/ 22/undocumented-query-plans-égalité-comparaisons.aspx

24
John Keller

Si votre implémentation SQL n'implémente pas les opérateurs standard SQL IS DISTINCT FROM et IS NOT DISTINCT FROM, vous pouvez réécrire les expressions les contenant en utilisant les équivalences suivantes:

En général:

a IS DISTINCT FROM b <==>
(
    ((a) IS NULL AND (b) IS NOT NULL)
OR
    ((a) IS NOT NULL AND (b) IS NULL)
OR
    ((a) <> (b))
)

a IS NOT DISTINCT FROM b <==>
(
    ((a) IS NULL AND (b) IS NULL)
OR
    ((a) = (b))
)

Cette réponse est incorrecte lorsqu'elle est utilisée dans un contexte où la différence entre UNKNOWN et FALSE est importante. Je pense que c'est rare, cependant. Voir la réponse acceptée par @ChrisBandy.

S'il est possible d'identifier une valeur d'espace réservé qui ne se produit pas réellement dans les données, alors COALESCE est une alternative:

a IS DISTINCT FROM b <==> COALESCE(a, placeholder) <> COALESCE(b, placeholder)
a IS NOT DISTINCT FROM b <==> COALESCE(a, placeholder) = COALESCE(b, placeholder)
11
Jason Kresowaty

Un inconvénient à la réécriture IS DISTINCT FROM et IS NOT DISTINCT FROM serait de ne pas interférer avec l'utilisation des index, du moins avec SQL Server. En d'autres termes, lorsque vous utilisez les éléments suivants:

WHERE COALESCE(@input, x) = COALESCE(column, x)

SQL Server ne pourra utiliser aucun index incluant column . Donc, dans une clause WHERE, il serait préférable d’utiliser le formulaire

WHERE @input = column OR (@input IS NULL AND column IS NULL)

pour tirer parti de tous les index pour column . (Parens utilisé uniquement pour plus de clarté)

5
Boyd

Pour la référence, l'implémentation la plus canonique (et lisible) de IS [ NOT ] DISTINCT FROM serait une expression CASE bien formatée. Pour IS DISTINCT FROM:

CASE WHEN [a] IS     NULL AND [b] IS     NULL THEN FALSE
     WHEN [a] IS     NULL AND [b] IS NOT NULL THEN TRUE
     WHEN [a] IS NOT NULL AND [b] IS     NULL THEN TRUE
     WHEN [a] =               [b]             THEN FALSE
     ELSE                                          TRUE
END

Évidemment, d’autres solutions (en particulier de John Keller , utilisant INTERSECT) sont plus concises.

Plus de détails ici

1
Lukas Eder

Juste pour prolonger John Keller réponse. Je préfère utiliser les modèles EXISTS et EXCEPT:

a IS DISTINCT FROM b
<=>
EXISTS (SELECT a EXCEPT SELECT b)
-- NOT EXISTS (SELECT a INTERSECT SELECT b)

et

a IS NOT DISTINCT FROM  b
<=>
NOT EXISTS (SELECT a EXCEPT SELECT b)
-- EXISTS (SELECT a INTERSECT SELECT b)

pour une raison particulière. NOT est aligné alors que avec INTERSECT il est inversé.


SELECT 1 AS PK, 21 AS c, NULL  AS  b
INTO tab1;

SELECT 1 AS PK, 21 AS c, 2 AS b
INTO tab2;

SELECT *
FROM tab1 A
JOIN tab2 B ON A.PK = B.PK
WHERE EXISTS(SELECT A.c, A.B
              EXCEPT
              SELECT B.c, B.b);

DBFiddle Demo

1
Lukasz Szozda
a IS NOT DISTINCT FROM b

peut être réécrit comme:

(a IS NOT NULL AND b IS NOT NULL AND a=b) OR (a IS NULL AND b is NULL)

a IS DISTINCT FROM b

peut être réécrit comme:

NOT (a IS NOT DISTINCT FROM b)
0
wojtek

Ces expressions peuvent remplacer avantageusement la logique IS DISTINCT FROM et donner de meilleurs résultats que les exemples précédents, car elles sont finalement compilées par le serveur SQL en une seule expression de prédicat, ce qui donne env. la moitié du coût d'opérateur sur une expression de filtre. Elles sont essentiellement les mêmes que celles proposées par Chris Bandy, mais elles utilisent des fonctions imbriquées ISNULL et NULLIF pour effectuer les comparaisons sous-jacentes. 

(... évidemment, ISNULL pourrait être remplacé par COALESCE si vous préférez)

  • a IS DISTINCT FROM b peut être réécrit comme:

    ISNULL(NULLIF(a, b), NULLIF(b, a)) IS NOT NULL

  • a IS NOT DISTINCT FROM b peut être réécrit comme:

    ISNULL(NULLIF(a, b), NULLIF(b, a)) IS NULL

0
Jason