NULL-Werte in der NOT IN-Klausel

244

Dieses Problem trat auf, als ich unterschiedliche Datensatzzählungen für identische Abfragen erhielt, von denen eine eine not in whereEinschränkung und die andere eine Einschränkung verwendete left join. Die Tabelle in der not inEinschränkung hatte einen Nullwert (fehlerhafte Daten), wodurch diese Abfrage eine Anzahl von 0 Datensätzen zurückgab. Ich verstehe irgendwie warum, aber ich könnte Hilfe gebrauchen, um das Konzept vollständig zu verstehen.

Um es einfach auszudrücken: Warum gibt Abfrage A ein Ergebnis zurück, B jedoch nicht?

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

Dies war unter SQL Server 2005. Ich habe auch festgestellt, dass beim Aufruf von set ansi_nulls offB ein Ergebnis zurückgegeben wird.

Jamie Ide
quelle

Antworten:

283

Abfrage A ist dasselbe wie:

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

Da 3 = 3ist wahr, erhalten Sie ein Ergebnis.

Abfrage B ist dieselbe wie:

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

Wenn aktiviert ansi_nullsist, 3 <> nullist UNBEKANNT, sodass das Prädikat als UNBEKANNT ausgewertet wird und Sie keine Zeilen erhalten.

Wenn ansi_nullsdeaktiviert ist, 3 <> nullist wahr, sodass das Prädikat als wahr ausgewertet wird und Sie eine Zeile erhalten.

Brannon
quelle
11
Hat jemals jemand darauf hingewiesen, dass die Konvertierung NOT INin eine Reihe von <> andÄnderungen das semantische Verhalten von nicht in dieser Menge auf etwas anderes ändert ?
Ian Boyd
8
@Ian - Es sieht so aus, als ob "A NOT IN ('X', 'Y')" tatsächlich ein Alias ​​für A <> 'X' UND A <> 'Y' in SQL ist. (Ich sehe, dass Sie dies selbst in stackoverflow.com/questions/3924694/… entdeckt haben , aber sicherstellen wollten, dass Ihr Einwand in dieser Frage angesprochen wurde.)
Ryan Olson
Ich denke, dies erklärt, warum SELECT 1 WHERE NULL NOT IN (SELECT 1 WHERE 1=0);eine Zeile anstelle der von mir erwarteten leeren Ergebnismenge ausgegeben wird.
Binki
2
Dies ist ein sehr schlechtes Verhalten von SQL Server, denn wenn es einen NULL-Vergleich mit "IS NULL" erwartet, sollte es die IN-Klausel auf dasselbe Verhalten erweitern und nicht dumm die falsche Semantik auf sich selbst anwenden.
OzrenTkalcecKrznaric
@binki, Ihre Abfrage wird ausgeführt, wenn Sie hier rextester.com/l/sql_server_online_compiler ausführen, funktioniert jedoch nicht, wenn Sie hier sqlcourse.com/cgi-bin/interpreter.cgi ausführen .
Istiaque Ahmed
52

Wann immer Sie NULL verwenden, haben Sie es wirklich mit einer dreiwertigen Logik zu tun.

Ihre erste Abfrage gibt Ergebnisse zurück, wenn die WHERE-Klausel Folgendes ergibt:

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

Der zweite:

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

Das UNBEKANNTE ist nicht dasselbe wie FALSE. Sie können es einfach testen, indem Sie Folgendes aufrufen:

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

Beide Abfragen führen zu keinen Ergebnissen

Wenn UNBEKANNT dasselbe wie FALSCH wäre, müsste unter der Annahme, dass die erste Abfrage FALSCH ergibt, die zweite als WAHR ausgewertet werden, da es dasselbe wie NICHT (FALSCH) gewesen wäre.
Das ist nicht der Fall.

Es gibt einen sehr guten Artikel zu diesem Thema auf SqlServerCentral .

Die gesamte Ausgabe von NULLs und Three-Valued Logic kann zunächst etwas verwirrend sein, aber es ist wichtig zu verstehen, um korrekte Abfragen in TSQL zu schreiben

Ein weiterer Artikel, den ich empfehlen würde, ist SQL Aggregate Functions und NULL .

kristof
quelle
33

NOT IN Gibt 0 Datensätze zurück, wenn sie mit einem unbekannten Wert verglichen werden

Da dies NULLunbekannt ist, gibt eine NOT INAbfrage, die ein NULLoder NULLs in der Liste der möglichen Werte enthält, immer 0Datensätze zurück, da nicht sichergestellt werden kann, dass der NULLWert nicht der getestete Wert ist.

YonahW
quelle
3
Dies ist die Antwort auf den Punkt gebracht. Ich fand das auch ohne Beispiel leichter zu verstehen.
Govind Rai
18

Der Vergleich mit null ist undefiniert, es sei denn, Sie verwenden IS NULL.

Wenn Sie also 3 mit NULL vergleichen (Abfrage A), wird undefiniert zurückgegeben.

Dh SELECT 'true' wobei 3 in (1,2, null) und SELECT 'true' wo 3 nicht in (1,2, null)

führt zu demselben Ergebnis, da NOT (UNDEFINED) immer noch undefiniert, aber nicht TRUE ist

Sunny Milenov
quelle
Toller Punkt. Wählen Sie 1, wobei null in (null) keine Zeilen (ansi) zurückgibt.
Crokusek
9

Der Titel dieser Frage zum Zeitpunkt des Schreibens lautet

SQL NOT IN-Einschränkung und NULL-Werte

Aus dem Text der Frage geht hervor, dass das Problem SELECTeher in einer SQL-DML- Abfrage als in einer SQL-DDL aufgetreten ist CONSTRAINT.

Insbesondere angesichts des Wortlauts des Titels möchte ich jedoch darauf hinweisen, dass einige der hier gemachten Aussagen möglicherweise irreführende Aussagen sind, die in Anlehnung an (Paraphrasierung)

Wenn das Prädikat UNBEKANNT ergibt, erhalten Sie keine Zeilen.

Obwohl dies bei SQL DML der Fall ist, ist der Effekt bei Berücksichtigung von Einschränkungen unterschiedlich.

Betrachten Sie diese sehr einfache Tabelle mit zwei Einschränkungen, die direkt den Prädikaten in der Frage entnommen wurden (und in einer ausgezeichneten Antwort von @Brannon angesprochen wurden):

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;

Gemäß der Antwort von @ Brannon wird die erste Einschränkung (using IN) als TRUE und die zweite Einschränkung (using NOT IN) als UNKNOWN ausgewertet. Der Einsatz ist jedoch erfolgreich! Daher ist es in diesem Fall nicht unbedingt richtig zu sagen, "Sie erhalten keine Zeilen", da wir tatsächlich eine Zeile als Ergebnis eingefügt haben.

Der obige Effekt ist in Bezug auf den SQL-92-Standard tatsächlich der richtige. Vergleichen und kontrastieren Sie den folgenden Abschnitt aus der SQL-92-Spezifikation

7.6 where-Klausel

Das Ergebnis von ist eine Tabelle der Zeilen von T, für die das Ergebnis der Suchbedingung wahr ist.

4.10 Integritätsbeschränkungen

Eine Tabellenprüfungsbeschränkung ist genau dann erfüllt, wenn die angegebene Suchbedingung für keine Zeile einer Tabelle falsch ist.

Mit anderen Worten:

In SQL DML werden Zeilen aus dem Ergebnis entfernt, wenn die WHEREAuswertung UNBEKANNT ist, da sie die Bedingung "ist wahr" nicht erfüllt.

In SQL DDL (dh Einschränkungen) werden Zeilen nicht aus dem Ergebnis entfernt , wenn sie zu UNKNOWN ausgewertet werden, weil sie nicht die Bedingung erfüllen „nicht falsch ist“.

Obwohl die Auswirkungen in SQL DML bzw. SQL DDL widersprüchlich erscheinen mögen, gibt es einen praktischen Grund, UNBEKANNTEN Ergebnissen den "Vorteil des Zweifels" zu geben, indem sie eine Bedingung erfüllen können (genauer gesagt, damit sie eine Einschränkung nicht verfehlen können). : Ohne dieses Verhalten müssten alle Einschränkungen explizit mit Nullen umgehen, und das wäre aus Sicht des Sprachdesigns sehr unbefriedigend (ganz zu schweigen von einem richtigen Schmerz für Programmierer!).

ps Wenn Sie es als schwierig empfinden, einer Logik wie "Unbekannt erfüllt eine Einschränkung nicht" zu folgen, wie ich sie schreiben soll, sollten Sie darauf verzichten, indem Sie einfach nullbare Spalten in SQL DDL und alles in SQL vermeiden DML, die Nullen erzeugt (z. B. äußere Verknüpfungen)!

eines Tages, wenn
quelle
Ich habe ehrlich gesagt nicht gedacht, dass zu diesem Thema noch etwas zu sagen ist. Interessant.
Jamie Ide
2
@Jamie Ide: Eigentlich habe ich eine andere Antwort zu diesem Thema: Da das NOT IN (subquery)Einbeziehen von Nullen zu unerwarteten Ergebnissen führen kann, ist es verlockend, diese IN (subquery)vollständig zu vermeiden und immer zu verwenden NOT EXISTS (subquery)(wie ich es einmal getan habe!), Da es den Anschein hat, dass Nullen immer korrekt behandelt werden. Es gibt jedoch Fälle, in denen NOT IN (subquery)das erwartete Ergebnis NOT EXISTS (subquery)erzielt wird, während unerwartete Ergebnisse erzielt werden! Ich kann dies vielleicht noch aufschreiben, wenn ich meine Notizen zu diesem Thema finde (ich brauche Notizen, weil es nicht intuitiv ist!). Die Schlussfolgerung ist jedoch dieselbe: Vermeiden Sie Nullen!
Tag, wenn
@onedaywhen Ich bin verwirrt von Ihrer Behauptung, dass NULL in einem speziellen Fall sein müsste, um ein konsistentes Verhalten zu haben (intern konsistent, nicht konsistent mit der Spezifikation). Wäre es nicht ausreichend, 4.10 in "Eine Tabellenprüfungsbedingung ist genau dann erfüllt, wenn die angegebene Suchbedingung erfüllt ist" zu ändern?
DylanYoung
@DylanYoung: Nein, die Spezifikation ist aus einem entscheidenden Grund so formuliert: SQL leidet unter einer Drei-Werte-Logik, wo diese Werte sind TRUE, FALSEund UNKNOWN. Ich nehme an, 4.10 hätte lauten können: "Eine Tabellenprüfungsbeschränkung ist genau dann erfüllt, wenn die angegebene Suchbedingung für jede Zeile einer Tabelle WAHR oder UNBEKANNT ist" - beachten Sie meine Änderung am Ende des Satzes - die Sie weggelassen haben - - von "für jeden" bis "für alle". Ich habe das Bedürfnis, die logischen Werte zu kapitalisieren, weil sich die Bedeutung von "wahr" und "falsch" in der natürlichen Sprache sicherlich auf die klassische zweiwertige Logik beziehen muss.
Tag, wenn der
1
Bedenken Sie: CREATE TABLE T ( a INT NOT NULL UNIQUE, b INT CHECK( a = b ) );- Die Absicht hier ist, dass bentweder gleich aoder null sein muss. Wenn eine Einschränkung TRUE ergeben müsste, um erfüllt zu werden, müssten wir die Einschränkung ändern, um explizit Nullen zu behandeln, z CHECK( a = b OR b IS NULL ). Daher müsste für jede Einschränkung ...OR IS NULLvom Benutzer eine Logik für jede betroffene nullfähige Spalte hinzugefügt werden: mehr Komplexität, mehr Fehler, wenn sie dies vergessen haben usw. Ich denke, das SQL-Standardkomitee hat nur versucht, pragmatisch zu sein.
Tag, wenn
7

In A wird 3 auf Gleichheit mit jedem Mitglied der Menge getestet, was ergibt (FALSE, FALSE, TRUE, UNKNOWN). Da eines der Elemente WAHR ist, ist die Bedingung WAHR. (Es ist auch möglich, dass hier ein Kurzschluss auftritt, sodass er tatsächlich stoppt, sobald er die erste WAHR trifft und niemals 3 = NULL auswertet.)

In B wird die Bedingung meiner Meinung nach als NICHT bewertet (3 in (1,2, null)). Testen von 3 auf Gleichheit mit den festgelegten Ausbeuten (FALSE, FALSE, UNKNOWN), die zu UNKNOWN aggregiert werden. NOT (UNKNOWN) ergibt UNKNOWN. Insgesamt ist also die Wahrheit des Zustands unbekannt, was am Ende im Wesentlichen als FALSCH behandelt wird.

Dave Costa
quelle
7

Aus den Antworten hier kann geschlossen werden, dass NOT IN (subquery)Nullen nicht richtig behandelt werden und zugunsten von vermieden werden sollten NOT EXISTS. Eine solche Schlussfolgerung kann jedoch verfrüht sein. In dem folgenden Szenario, das Chris Date (Database Programming and Design, Band 2, Nr. 9, September 1989) NOT INgutgeschrieben wurde, werden Nullen korrekt behandelt und das richtige Ergebnis zurückgegeben, anstatt NOT EXISTS.

Betrachten Sie eine Tabelle sp, um Lieferanten ( sno) darzustellen , von denen bekannt ist, dass sie Teile ( pno) in Menge ( qty) liefern . Die Tabelle enthält derzeit die folgenden Werte:

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

Beachten Sie, dass die Menge nullbar ist, dh um erfassen zu können, dass ein Lieferant bekanntermaßen Teile liefert, auch wenn nicht bekannt ist, in welcher Menge.

Die Aufgabe besteht darin, die Lieferanten zu finden, deren Lieferteilnummer 'P1' bekannt ist, jedoch nicht in Mengen von 1000.

Folgendes wird verwendet NOT IN, um nur den Lieferanten 'S2' korrekt zu identifizieren:

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'
                       );

Die folgende Abfrage verwendet jedoch dieselbe allgemeine Struktur, enthält NOT EXISTSjedoch fälschlicherweise den Lieferanten 'S1' im Ergebnis (dh für den die Menge null ist):

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
                      );

Also NOT EXISTSist nicht die Silberkugel, die es erschienen sein könnte!

Die Ursache des Problems ist natürlich das Vorhandensein von Nullen, daher besteht die "echte" Lösung darin, diese Nullen zu beseitigen.

Dies kann (neben anderen möglichen Designs) anhand von zwei Tabellen erreicht werden:

  • sp Lieferanten, von denen bekannt ist, dass sie Teile liefern
  • spq Lieferanten, von denen bekannt ist, dass sie Teile in bekannten Mengen liefern

Beachten Sie, dass es wahrscheinlich eine Fremdschlüsseleinschränkung geben sollte, auf die spqverwiesen wird sp.

Das Ergebnis kann dann mit dem Vergleichsoperator 'minus' (der das EXCEPTSchlüsselwort in Standard SQL ist) erhalten werden, z

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;
eines Tages, wenn
quelle
1
Oh mein Gott. Vielen Dank, dass Sie dies tatsächlich geschrieben haben ... das hat mich verrückt gemacht ...
Govind Rai
6

Null bedeutet und das Fehlen von Daten, das heißt, es ist unbekannt, kein Datenwert von nichts. Es ist für Leute mit Programmierhintergrund sehr leicht, dies zu verwechseln, da in Sprachen vom Typ C bei Verwendung von Zeigern null in der Tat nichts ist.

Daher befindet sich 3 im ersten Fall tatsächlich in der Menge von (1,2,3, null), so dass true zurückgegeben wird

Im zweiten können Sie es jedoch auf reduzieren

Wählen Sie 'true', wobei 3 nicht in (null)

Es wird also nichts zurückgegeben, da der Parser nichts über die Menge weiß, mit der Sie es vergleichen - es ist keine leere Menge, sondern eine unbekannte Menge. Die Verwendung von (1, 2, null) hilft nicht weiter, da die Menge (1,2) offensichtlich falsch ist, aber dann tun Sie das gegen Unbekanntes, was unbekannt ist.

Cruachan
quelle
6

Wenn Sie mit NOT IN nach einer Unterabfrage filtern möchten, die NULL-Werte enthält, aktivieren Sie das Kontrollkästchen nicht null

SELECT blah FROM t WHERE blah NOT IN
        (SELECT someotherBlah FROM t2 WHERE someotherBlah IS NOT NULL )
Mihai
quelle
Ich hatte ein Problem mit der Outer Join-Abfrage, bei der in bestimmten Situationen keine Datensätze zurückgegeben wurden. Überprüfen Sie diese Lösung auf das Szenario "Null" und "Vorhandene Datensätze" und es funktionierte für mich. Wenn andere Probleme auftreten, werde ich hier erwähnt. Vielen Dank.
QMaster
1

das ist für Boy:

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

Dies funktioniert unabhängig von den Ansi-Einstellungen

CB
quelle
für die ursprüngliche Frage: B: Wählen Sie 'true', wobei 3 nicht in (1, 2, null) ist. Um Nullen zu entfernen, müssen Sie z. B. 'true' auswählen, wobei 3 nicht in (1, 2, isnull (null, 0) ist. ) Die allgemeine Logik lautet: Wenn NULL die Ursache ist, finden Sie einen Weg, um NULL-Werte in einem bestimmten Schritt der Abfrage zu entfernen.
Wählen Sie party_code von abc als wo party_code nicht in (wählen Sie party_code von xyz, wo party_code nicht null ist), aber viel Glück, wenn Sie vergessen haben, dass das Feld Nullen zulässt, was häufig der Fall ist
1

SQL verwendet dreiwertige Logik für Wahrheitswerte. Die INAbfrage erzeugt das erwartete Ergebnis:

SELECT * FROM (VALUES (1), (2)) AS tbl(col) WHERE col IN (NULL, 1)
-- returns first row

Das Hinzufügen von a NOTkehrt die Ergebnisse jedoch nicht um:

SELECT * FROM (VALUES (1), (2)) AS tbl(col) WHERE NOT col IN (NULL, 1)
-- returns zero rows

Dies liegt daran, dass die obige Abfrage der folgenden entspricht:

SELECT * FROM (VALUES (1), (2)) AS tbl(col) WHERE NOT (col = NULL OR col = 1)

So wird die where-Klausel bewertet:

| col | col = NULL (1) | col = 1 | col = NULL OR col = 1 | NOT (col = NULL OR col = 1) |
|-----|----------------|---------|-----------------------|-----------------------------|
| 1   | UNKNOWN        | TRUE    | TRUE                  | FALSE                       |
| 2   | UNKNOWN        | FALSE   | UNKNOWN (2)           | UNKNOWN (3)                 |

Beachte das:

  1. Der Vergleich mit NULLErträgenUNKNOWN
  2. Der ORAusdruck, bei dem keiner der Operanden TRUEund mindestens ein Operand vorhanden ist, UNKNOWNergibt UNKNOWN( ref )
  3. Die NOTvon UNKNOWNAusbeuten UNKNOWN( ref )

Sie können das obige Beispiel auf mehr als zwei Werte erweitern (z. B. NULL, 1 und 2), aber das Ergebnis ist dasselbe: Wenn einer der Werte ist, NULLstimmt keine Zeile überein.

Salman A.
quelle