Question

Ce problème est survenu lorsque différents comptes ont été enregistrés pour ce que je pensais être des requêtes identiques, l'une utilisant une contrainte not in where et l'autre un left join. La table dans la contrainte set ansi_nulls off avait une valeur null (données incorrectes), ce qui a amené cette requête à renvoyer un nombre de 0 enregistrements. Je comprends un peu pourquoi mais je pourrais utiliser un peu d’aide pour bien comprendre le concept.

Pour le dire simplement, pourquoi la requête A renvoie-t-elle un résultat mais pas B?

A: select 'true' where 3 in (1, 2, 3, null)
B: select 'true' where 3 not in (1, 2, null)

C'était sur SQL Server 2005. J'ai également constaté que l'appel de <=> obligeait B à renvoyer un résultat.

Était-ce utile?

La solution

La requête A est identique à:

select 'true' where 3 = 1 or 3 = 2 or 3 = 3 or 3 = null

Puisque 3 = 3 est vrai, vous obtenez un résultat.

La requête B est identique à:

select 'true' where 3 <> 1 and 3 <> 2 and 3 <> null

Lorsque ansi_nulls est activé, 3 <> null est inconnu, le prédicat est évalué comme inconnu et vous n'obtenez aucune ligne.

Lorsque <=> est désactivé, <=> est défini sur true, le prédicat est évalué à true et vous obtenez une ligne.

Autres conseils

Chaque fois que vous utilisez NULL, vous avez vraiment affaire à une logique à trois valeurs.

Votre première requête renvoie les résultats lorsque la clause WHERE est évaluée à:

    3 = 1 or 3 = 2 or 3 = 3 or 3 = null
which is:
    FALSE or FALSE or TRUE or UNKNOWN
which evaluates to 
    TRUE

Le second:

    3 <> 1 and 3 <> 2 and 3 <> null
which evaluates to:
    TRUE and TRUE and UNKNOWN
which evaluates to:
    UNKNOWN

Le UNKNOWN est différent de FALSE vous pouvez facilement le tester en appelant:

select 'true' where 3 <> null
select 'true' where not (3 <> null)

Les deux requêtes ne vous donneront aucun résultat

Si l'inconnu était identique à FALSE, en supposant que la première requête vous donnerait FALSE, la seconde devrait être évaluée à TRUE, car elle aurait été identique à NOT (FALSE).
Ce n'est pas le cas.

Il existe un très bon article sur ce sujet sur SqlServerCentral . .

Toute la question des valeurs NULL et de la logique à trois valeurs peut être un peu déroutante au début, mais il est essentiel de la comprendre pour pouvoir écrire des requêtes correctes dans TSQL

Un autre article que je recommanderais est le Fonctions d'agrégation SQL et NULL . .

NOT IN renvoie 0 enregistrement en comparaison avec une valeur inconnue

Etant donné que NULL est une inconnue, une requête 0 contenant un <=> ou un <=> dans la liste des valeurs possibles renverra toujours <=> enregistrements, car il n'existe aucun moyen de s'assurer que le < => valeur n'est pas la valeur testée.

La comparaison avec null n'est pas définie, sauf si vous utilisez IS NULL.

Ainsi, lorsque vous comparez 3 à NULL (requête A), il renvoie non défini.

I.e. SELECT 'true' où 3 in (1,2, null)  et SELECT 'true' où 3 not in (1,2, null)

produira le même résultat, car NOT (UNDEFINED) n'est toujours pas défini, mais n'est pas VRAI

Le titre de cette question au moment de la rédaction est

  

Contrainte SQL NOT IN et valeurs NULL

D'après le texte de la question, il semble que le problème se produise dans une requête SQL DML SELECT plutôt que dans un langage DDL SQL CONSTRAINT.

Cependant, étant donné le libellé du titre, je tiens à souligner que certaines déclarations faites ici sont des déclarations potentiellement trompeuses, à l'instar de (paraphrasant)

  

Lorsque le prédicat est évalué sur UNKNOWN, vous n'obtenez aucune ligne.

Bien que ce soit le cas pour SQL DML, l'effet est différent lors de la prise en compte des contraintes.

Considérez ce tableau très simple avec deux contraintes tirées directement des prédicats de la question (et traitées dans une excellente réponse de @Brannon):

DECLARE @T TABLE 
(
 true CHAR(4) DEFAULT 'true' NOT NULL, 
 CHECK ( 3 IN (1, 2, 3, NULL )), 
 CHECK ( 3 NOT IN (1, 2, NULL ))
);

INSERT INTO @T VALUES ('true');

SELECT COUNT(*) AS tally FROM @T;

Selon la réponse de @ Brannon, la première contrainte (utilisant IN) est évaluée à TRUE et la deuxième contrainte (utilisant NOT IN) est évaluée à UNKNOWN. Cependant , l'insertion réussit! Par conséquent, dans ce cas, il n’est pas strictement correct de dire & "Vous n’obtenez aucune ligne &"; car nous avons en effet une ligne insérée à la suite.

L’effet ci-dessus est bien le bon en ce qui concerne le standard SQL-92. Comparez et contrastez la section suivante de la spécification SQL-92

  

7.6 où la clause

     

Le résultat de est un tableau de ces lignes de T pour   dont le résultat de la condition de recherche est vrai.

     

4.10 Contraintes d'intégrité

     

Une contrainte de vérification de table est satisfaite si et seulement si la   La condition de recherche n'est pas fausse pour toutes les lignes d'une table.

En d'autres termes:

Dans le langage DML SQL, les lignes sont supprimées du résultat lorsque WHERE est considéré comme inconnu, car il ne répond pas à la condition & "est vraie &";

Dans SQL DDL (c'est-à-dire les contraintes), les lignes ne sont pas supprimées du résultat lorsqu'elles sont évaluées en tant que UNKNOWN car elles ne répondent pas à la condition & "n'est pas faux &";

Bien que les effets respectifs dans SQL DML et SQL DDL puissent sembler contradictoires, il existe une raison pratique de donner le bénéfice du doute aux résultats de UNKNOWN, en leur permettant de satisfaire à une contrainte (plus correctement, en leur permettant de ne pas une contrainte): sans ce comportement, toutes les contraintes devraient gérer explicitement les valeurs nulles, ce qui serait très peu satisfaisant du point de vue de la conception du langage (sans parler du fait que c’est un problème pour les programmeurs!)

p.s. si vous trouvez aussi difficile de suivre une logique telle que & "Inconnu ne manque pas de satisfaire une contrainte &"; comme je dois l'écrire, alors considérez que vous pouvez vous passer de tout cela en évitant simplement les colonnes nullables dans le DDL SQL et tout élément dans le langage DML SQL qui produit des valeurs NULL (par exemple, des jointures externes)!

Dans A, 3 est testé pour l’égalité par rapport à chaque membre de l’ensemble, donnant (FALSE, FALSE, TRUE, UNKNOWN). Comme l'un des éléments est VRAI, la condition est VRAIE. (Il est également possible qu'un court-circuit se produise ici, il s'arrête donc dès qu'il atteint le premier VRAI et n'évalue jamais 3 = NULL.)

En B, je pense que la condition est évaluée par NOT (3 in (1,2, null)). Test 3 d'égalité par rapport aux rendements définis (FALSE, FALSE, UNKNOWN), qui est agrégé à UNKNOWN. NOT (INCONNU) donne INCONNU. Donc dans l’ensemble, la vérité de la maladie est inconnue, ce qui à la fin est essentiellement traitée comme FAUX.

Null signifie et absence de données, c’est-à-dire qu’il est inconnu et non une valeur de donnée nulle. Il est très facile pour les gens de la programmation d’avoir mal à comprendre cela parce que, dans les langages de type C, utiliser des pointeurs nuls n’est en effet rien.

Donc dans le premier cas 3 est bien dans l'ensemble de (1,2,3, null) donc true est renvoyé

Dans la seconde cependant, vous pouvez le réduire à

sélectionnez 'true' où 3 ne figure pas dans (null)

Donc, rien n’est retourné car l’analyseur ignore tout de l’ensemble auquel vous le comparez - ce n’est pas un ensemble vide, mais un ensemble inconnu. L'utilisation de (1, 2, null) n'aide pas, car l'ensemble (1,2) est évidemment faux, mais vous vous en tenez à un inconnu, ce qui est inconnu.

On peut conclure des réponses fournies que NOT IN (subquery) ne gère pas correctement les valeurs NULL et doit être évité au profit de NOT EXISTS. Cependant, une telle conclusion peut être prématurée. Dans le scénario suivant, attribué à Chris Date (Conception et programmation de la base de données, vol 2 n ° 9 de septembre 1989), NOT IN gère correctement les valeurs NULL et renvoie le résultat correct au lieu de sp.

Considérons un tableau sno représentant les fournisseurs (pno) connus pour fournir des pièces (qty) en quantité (spq). La table contient actuellement les valeurs suivantes:

      VALUES ('S1', 'P1', NULL), 
             ('S2', 'P1', 200),
             ('S3', 'P1', 1000)

Notez que la quantité est annulable, c’est-à-dire pour pouvoir enregistrer le fait qu’un fournisseur est connu pour fournir des pièces, même si on ne sait pas en quelle quantité.

La tâche consiste à rechercher les fournisseurs connus sous le numéro de pièce 'P1', mais pas par milliers.

Ce qui suit utilise EXCEPT pour identifier correctement le fournisseur «S2» uniquement:

WITH sp AS 
     ( SELECT * 
         FROM ( VALUES ( 'S1', 'P1', NULL ), 
                       ( 'S2', 'P1', 200 ),
                       ( 'S3', 'P1', 1000 ) )
              AS T ( sno, pno, qty )
     )
SELECT DISTINCT spx.sno
  FROM sp spx
 WHERE spx.pno = 'P1'
       AND 1000 NOT IN (
                        SELECT spy.qty
                          FROM sp spy
                         WHERE spy.sno = spx.sno
                               AND spy.pno = 'P1'
                       );

Cependant, la requête ci-dessous utilise la même structure générale mais avec <=> mais inclut incorrectement le fournisseur 'S1' dans le résultat (c'est-à-dire pour lequel la quantité est nulle):

WITH sp AS 
     ( SELECT * 
         FROM ( VALUES ( 'S1', 'P1', NULL ), 
                       ( 'S2', 'P1', 200 ),
                       ( 'S3', 'P1', 1000 ) )
              AS T ( sno, pno, qty )
     )
SELECT DISTINCT spx.sno
  FROM sp spx
 WHERE spx.pno = 'P1'
       AND NOT EXISTS (
                       SELECT *
                         FROM sp spy
                        WHERE spy.sno = spx.sno
                              AND spy.pno = 'P1'
                              AND spy.qty = 1000
                      );

Alors <=> n'est pas la solution miracle, il est peut-être apparu!

Bien sûr, le problème est dû à la présence de valeurs NULL. La solution "réelle" consiste donc à éliminer ces valeurs.

Ceci peut être réalisé (entre autres conceptions possibles) en utilisant deux tableaux:

  • <=> fournisseurs connus pour fournir des pièces
  • <=> fournisseurs connus pour fournir des pièces en quantités connues

notant qu'il devrait y avoir probablement une contrainte de clé étrangère où <=> fait référence à <=>.

Le résultat peut ensuite être obtenu à l'aide de l'opérateur relationnel "moins" (le mot clé <=> dans SQL standard), par exemple.

WITH sp AS 
     ( SELECT * 
         FROM ( VALUES ( 'S1', 'P1' ), 
                       ( 'S2', 'P1' ),
                       ( 'S3', 'P1' ) )
              AS T ( sno, pno )
     ),
     spq AS 
     ( SELECT * 
         FROM ( VALUES ( 'S2', 'P1', 200 ),
                       ( 'S3', 'P1', 1000 ) )
              AS T ( sno, pno, qty )
     )
SELECT sno
  FROM spq
 WHERE pno = 'P1'
EXCEPT 
SELECT sno
  FROM spq
 WHERE pno = 'P1'
       AND qty = 1000;

SI vous voulez filtrer avec NOT IN pour une sous-requête contenant des valeurs NULL, vérifiez simplement que la valeur n'est pas nulle

SELECT blah FROM t WHERE blah NOT IN
        (SELECT someotherBlah FROM t2 WHERE someotherBlah IS NOT NULL )

ceci est pour Boy:

select party_code 
from abc as a
where party_code not in (select party_code 
                         from xyz 
                         where party_code = a.party_code);

cela fonctionne quels que soient les paramètres ansi

cela pourrait également être utile pour connaître la différence logique entre rejoindre, existe et dans http://weblogs.sqlteam.com/mladenp/archive/ 2007/05/18 / 60210.aspx

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top