Ce problème est survenu lorsque j'ai obtenu différents nombres d'enregistrements pour ce que je pensais être des requêtes identiques, l'une utilisant une not in
where
contrainte et l'autre a left join
. La table dans la not in
contrainte avait une valeur nulle (données incorrectes) qui a provoqué le renvoi de 0 enregistrements à cette requête. Je comprends en quelque sorte pourquoi mais je pourrais utiliser de l'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 set ansi_nulls off
obligeait B à retourner un résultat.
NOT IN
en une série de<> and
changements modifie le comportement sémantique de pas dans cet ensemble à autre chose?SELECT 1 WHERE NULL NOT IN (SELECT 1 WHERE 1=0);
donne une ligne au lieu du jeu de résultats vide auquel je m'attendais.Chaque fois que vous utilisez NULL, vous avez vraiment affaire à une logique à trois valeurs.
Votre première requête renvoie des résultats lorsque la clause WHERE est évaluée comme suit:
Le deuxième:
L'INCONNU n'est pas le même que FAUX, vous pouvez facilement le tester en appelant:
Les deux requêtes ne vous donneront aucun résultat
Si l'inconnu était le même que FAUX, alors en supposant que la première requête vous donnerait FAUX, la seconde devrait être évaluée comme VRAIE car elle aurait été la même que NON (FAUX).
Ce n'est pas le cas.
Il y a un très bon article sur ce sujet sur SqlServerCentral .
Tout le problème des NULL et de la logique à trois valeurs peut être un peu déroutant au début, mais il est essentiel de comprendre afin d'écrire des requêtes correctes dans TSQL
Un autre article que je recommanderais est SQL Aggregate Functions and NULL .
la source
NOT IN
renvoie 0 enregistrements par rapport à une valeur inconnueÉtant donné qu'il
NULL
s'agit d'un inconnu, uneNOT IN
requête contenant unNULL
ouNULL
s dans la liste des valeurs possibles retournera toujours des0
enregistrements car il n'y a aucun moyen de s'assurer que laNULL
valeur n'est pas la valeur en cours de test.la source
La comparaison avec null n'est pas définie, sauf si vous utilisez IS NULL.
Ainsi, lors de la comparaison de 3 à NULL (requête A), il renvoie undefined.
Soit SELECT 'vrai' où 3 pouces (1,2, null) et SELECT 'vrai' où 3 pas dans (1,2, null)
produira le même résultat, car NOT (UNDEFINED) n'est toujours pas défini, mais pas TRUE
la source
Le titre de cette question au moment de la rédaction est
D'après le texte de la question, il semble que le problème se produisait dans une
SELECT
requête SQL DML , plutôt que dans une DDL SQLCONSTRAINT
.Cependant, compte tenu en particulier du libellé du titre, je tiens à souligner que certaines déclarations faites ici sont des déclarations potentiellement trompeuses, celles qui s'inspirent de (paraphrasant)
Bien que ce soit le cas pour SQL DML, lorsque l'on considère les contraintes, l'effet est différent.
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):
Selon la réponse de @ Brannon, la première contrainte (utilisation
IN
) est évaluée à VRAI et la seconde contrainte (utilisationNOT IN
) est évaluée à INCONNU. Cependant , l'insert 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 inséré une ligne en conséquence.L'effet ci-dessus est en effet le bon en ce qui concerne la norme SQL-92. Comparez et contrastez la section suivante de la spécification SQL-92
En d'autres termes:
Dans SQL DML, les lignes sont supprimées du résultat lorsque l'
WHERE
évalué à INCONNU car il ne remplit pas la condition "est vrai".Dans SQL DDL (c'est-à-dire les contraintes), les lignes ne sont pas supprimées du résultat lorsqu'elles sont évaluées à UNKNOWN car il remplit la condition "n'est pas faux".
Bien que les effets dans SQL DML et SQL DDL respectivement puissent sembler contradictoires, il y a une raison pratique de donner aux résultats INCONNUS le `` bénéfice du doute '' en leur permettant de satisfaire une contrainte (plus correctement, en leur permettant de ne pas manquer de satisfaire une contrainte) : sans ce comportement, toutes les contraintes devraient gérer explicitement les valeurs nulles et ce serait très insatisfaisant du point de vue de la conception du langage (sans parler, une bonne douleur pour les codeurs!)
ps si vous trouvez difficile de suivre une logique telle que "inconnu ne manque pas de satisfaire une contrainte" comme je suis en train de l'écrire, alors considérez que vous pouvez vous passer de tout cela simplement en évitant les colonnes nullables en SQL DDL et quoi que ce soit en SQL DML qui produit des valeurs nulles (par exemple des jointures externes)!
la source
NOT IN (subquery)
peut donner des résultats inattendus, il est tentant d'éviterIN (subquery)
complètement et de toujours l'utiliserNOT EXISTS (subquery)
(comme je l'ai fait une fois!) Car il semble qu'il gère toujours correctement les null. Cependant, il y a des cas oùNOT IN (subquery)
donne le résultat attendu alors queNOT EXISTS (subquery)
donne des résultats inattendus! Je pourrai peut-être encore écrire ceci si je peux trouver mes notes sur le sujet (besoin de notes parce que ce n'est pas intuitif!) La conclusion est la même, cependant: évitez les nulls!TRUE
,FALSE
etUNKNOWN
. Je suppose que 4.10 aurait pu lire: "Une contrainte de vérification de table est satisfaite si et seulement si la condition de recherche spécifiée est VRAIE ou INCONNUE pour chaque ligne d'une table" - notez mon changement à la fin de la phrase - que vous avez omis - - de "pour tout" à "pour tous". Je ressens le besoin de capitaliser les valeurs logiques car le sens de "vrai" et "faux" dans le langage naturel doit sûrement se référer à la logique classique à deux valeurs.CREATE TABLE T ( a INT NOT NULL UNIQUE, b INT CHECK( a = b ) );
- l'intention ici est queb
doit être égala
ou nul. Si une contrainte devait aboutir à VRAI pour être satisfaite, alors nous aurions besoin de changer la contrainte pour gérer explicitement les valeurs nulles, par exempleCHECK( a = b OR b IS NULL )
. Ainsi, chaque contrainte devrait avoir une...OR IS NULL
logique ajoutée par l'utilisateur pour chaque colonne nullable impliquée: plus de complexité, plus de bogues quand ils ont oublié de le faire, etc. Je pense donc que le comité des normes SQL a juste essayé d'être pragmatique.En A, 3 est testé pour l'égalité par rapport à chaque membre de l'ensemble, ce qui donne (FAUX, FAUX, VRAI, INCONNU). Étant donné que l'un des éléments est VRAI, la condition est VRAIE. (Il est également possible qu'un court-circuit se produise ici, donc il s'arrête en fait dès qu'il atteint le premier TRUE et n'évalue jamais 3 = NULL.)
En B, je pense qu'il évalue la condition comme NON (3 in (1,2, null)). Test 3 de l'égalité par rapport aux rendements définis (FALSE, FALSE, UNKNOWN), qui est agrégé à UNKNOWN. NOT (UNKNOWN) donne INCONNU. Donc, dans l'ensemble, la vérité de la condition est inconnue, ce qui à la fin est essentiellement traité comme FAUX.
la source
On peut conclure des réponses ici qui
NOT IN (subquery)
ne traitent pas correctement les valeurs nulles et doivent être évitées en faveur deNOT EXISTS
. Cependant, une telle conclusion peut être prématurée. Dans le scénario suivant, attribué à Chris Date (Database Programming and Design, Vol 2 No 9, septembre 1989), c'estNOT IN
qu'il gère correctement les valeurs nulles et renvoie le résultat correct, plutôt queNOT EXISTS
.Considérons un tableau
sp
pour représenter les fournisseurs (sno
) qui sont connus pour fournir des pièces (pno
) en quantité (qty
). Le tableau contient actuellement les valeurs suivantes:Notez que la quantité peut être annulée, c'est-à-dire pour pouvoir enregistrer le fait qu'un fournisseur est connu pour fournir des pièces même s'il n'est pas connu en quelle quantité.
La tâche consiste à trouver les fournisseurs connus qui fournissent le numéro de pièce «P1» mais pas en quantités de 1000.
Les utilisations suivantes permettent
NOT IN
d'identifier correctement le fournisseur «S2» uniquement:Cependant, la requête ci-dessous utilise la même structure générale mais avec
NOT EXISTS
mais inclut incorrectement le fournisseur «S1» dans le résultat (c'est-à-dire pour lequel la quantité est nulle):Ce
NOT EXISTS
n'est donc pas la balle d'argent qu'elle a pu apparaître!Bien sûr, la source du problème est la présence de nulls, donc la «vraie» solution est d'éliminer ces nulls.
Ceci peut être réalisé (entre autres conceptions possibles) en utilisant deux tableaux:
sp
fournisseurs connus pour fournir des piècesspq
fournisseurs connus pour fournir des pièces en quantités connuesnotant qu'il devrait probablement y avoir une contrainte de clé étrangère où les
spq
référencessp
.Le résultat peut ensuite être obtenu en utilisant l'opérateur relationnel «moins» (qui est le
EXCEPT
mot clé dans SQL standard), par exemplela source
Null signifie et absence de données, c'est-à-dire qu'elles sont inconnues, pas une valeur de données de rien. Il est très facile pour les personnes issues de la programmation de confondre cela, car dans les langages de type C, lorsque vous utilisez des pointeurs, null n'est en effet rien.
Donc dans le premier cas, 3 est en effet dans l'ensemble de (1,2,3, nul) donc vrai est retourné
Dans la seconde, cependant, vous pouvez le réduire à
sélectionnez 'vrai' où 3 pas dans (null)
Donc rien n'est retourné parce que l'analyseur ne sait rien 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 manifestement faux, mais vous êtes contre et inconnu, ce qui est inconnu.
la source
SI vous voulez filtrer avec NOT IN pour une sous-requête contenant des NULL, vérifiez que non nul
la source
c'est pour Boy:
cela fonctionne indépendamment des paramètres ansi
la source
SQL utilise une logique à trois valeurs pour les valeurs de vérité. La
IN
requête produit le résultat attendu:Mais l'ajout d'un
NOT
n'inverse pas les résultats:En effet, la requête ci-dessus est équivalente à la suivante:
Voici comment la clause where est évaluée:
Remarquerez que:
NULL
rendementsUNKNOWN
OR
expression où aucun des opérandes ne se trouveTRUE
et au moins un opérande estUNKNOWN
renvoieUNKNOWN
( ref )NOT
desUNKNOWN
rendementsUNKNOWN
( ref )Vous pouvez étendre l'exemple ci-dessus à plus de deux valeurs (par exemple NULL, 1 et 2) mais le résultat sera le même: si l'une des valeurs est
NULL
alors aucune ligne ne correspondra.la source
cela peut également être utile pour connaître la différence logique entre la jointure, existe et dans http://weblogs.sqlteam.com/mladenp/archive/2007/05/18/60210.aspx
la source