SQL "select where not in subquery" gibt keine Ergebnisse zurück

130

Haftungsausschluss: Ich habe das Problem herausgefunden (glaube ich), aber ich wollte dieses Problem zu Stack Overflow hinzufügen, da ich es (leicht) nirgendwo finden konnte. Außerdem könnte jemand eine bessere Antwort haben als ich.

Ich habe eine Datenbank, in der eine Tabelle "Common" von mehreren anderen Tabellen referenziert wird. Ich wollte sehen, welche Datensätze in der Common-Tabelle verwaist waren (dh keine Referenzen aus einer der anderen Tabellen hatten).

Ich habe diese Abfrage ausgeführt:

select *
from Common
where common_id not in (select common_id from Table1)
and common_id not in (select common_id from Table2)

Ich weiß, dass es verwaiste Aufzeichnungen gibt, aber keine Aufzeichnungen zurückgegeben wurden. Warum nicht?

(Dies ist SQL Server, wenn es darauf ankommt.)

Jeremy Stein
quelle
Dieser stackoverflow.com/a/129152/1667619 beantwortet die WARUM-Frage ziemlich gut.
Ruchan

Antworten:

234

Aktualisieren:

Diese Artikel in meinem Blog beschreiben die Unterschiede zwischen den Methoden ausführlicher:


Es gibt drei Möglichkeiten, eine solche Abfrage durchzuführen:

  • LEFT JOIN / IS NULL::

    SELECT  *
    FROM    common
    LEFT JOIN
            table1 t1
    ON      t1.common_id = common.common_id
    WHERE   t1.common_id IS NULL
    
  • NOT EXISTS::

    SELECT  *
    FROM    common
    WHERE   NOT EXISTS
            (
            SELECT  NULL
            FROM    table1 t1
            WHERE   t1.common_id = common.common_id
            )
    
  • NOT IN::

    SELECT  *
    FROM    common
    WHERE   common_id NOT IN
            (
            SELECT  common_id
            FROM    table1 t1
            )
    

Wann table1.common_id nicht nullbar, sind alle diese Abfragen semantisch gleich.

Wenn es nullbar ist, NOT INist es anders, da IN(und daher NOT IN) zurückgegeben wird, NULLwenn ein Wert mit nichts in einer Liste übereinstimmt, die a enthältNULL .

Dies mag verwirrend sein, kann aber offensichtlicher werden, wenn wir uns an die alternative Syntax erinnern:

common_id = ANY
(
SELECT  common_id
FROM    table1 t1
)

Das Ergebnis dieser Bedingung ist ein boolesches Produkt aller Vergleiche innerhalb der Liste. Natürlich NULLergibt ein einzelner Wert das NULLErgebnis, das auch das gesamte Ergebnis NULLwiedergibt.

Wir können niemals definitiv sagen, dass common_iddies nichts aus dieser Liste entspricht, da mindestens einer der Werte ist NULL.

Angenommen, wir haben diese Daten:

common

--
1
3

table1

--
NULL
1
2

LEFT JOIN / IS NULLund NOT EXISTSwird zurückkehren 3, NOT INwird nichts zurückgeben (da es immer entweder FALSEoder auswerten wird NULL).

In MySQL, falls in nicht nullbaren Spalten, LEFT JOIN / IS NULLund NOT INsind ein wenig (mehrere Prozent) effizienter als NOT EXISTS. Wenn die Spalte nullbar ist,NOT EXISTS ist sie am effizientesten (wiederum nicht viel).

In ergeben Oraclealle drei Abfragen die gleichen Pläne (an ANTI JOIN).

In SQL Server, NOT IN/ NOT EXISTSsind effizienter, da LEFT JOIN / IS NULLes ANTI JOINdurch seinen Optimierer nicht zu einem optimiert werden kann.

In PostgreSQL, LEFT JOIN / IS NULLund NOT EXISTSsind effizienter als NOT INsine sie auf eine optimierte Anti Join, während NOT INAnwendungen hashed subplan(oder auch eine einfache , subplanwenn die Unterabfrage zu groß , um Hash ist)

Quassnoi
quelle
8
Gute Antwort! Vielen Dank!
StevenMcD
Das ist großartig und sehr hilfreich
Kavun
1
+1, weil mir diese Antwort viereinhalb Jahre später bei einem Problem geholfen hat, das mich verblüfft hat!
Carson63000
@ Carson63000 Snap! Ich dachte, ich würde verrückt, bevor ich diese Antwort sah
Bobby
1
@IstiaqueAhmed: Wird NOT EXISTSals TRUE ausgewertet, wenn die darin enthaltene Abfrage Zeilen zurückgibt. SELECT NULLkönnte auch SELECT *oder SELECT 1oder irgendetwas anderes sein, das NOT EXISTSPrädikat betrachtet die Werte der Zeilen nicht, sondern zählt sie nur.
Quassnoi
36

Wenn Sie möchten, dass die Welt ein zweiwertiger boolescher Ort ist, müssen Sie den Nullfall (dritter Wert) selbst verhindern.

Schreiben Sie keine IN-Klauseln, die Nullen auf der Listenseite zulassen. Filtern Sie sie heraus!

common_id not in
(
  select common_id from Table1
  where common_id is not null
)
Amy B.
quelle
6
Nullen in der In-Klausel-Liste sind ein häufiger Grund für fehlende Abfrageergebnisse.
Amy B
'Beim Vergleich mit einer Null ist die Antwort unbekannt' - aus der Antwort von @Jeremy Stein. Von common_id not inkönnen wir noch common_idWert haben, der ist NULL. Besteht das Problem, keine Ergebnisse zu erzielen, nicht weiterhin?
Istiaque Ahmed
5

Tabelle1 oder Tabelle2 enthält einige Nullwerte für common_id. Verwenden Sie stattdessen diese Abfrage:

select *
from Common
where common_id not in (select common_id from Table1 where common_id is not null)
and common_id not in (select common_id from Table2 where common_id is not null)
Jeremy Stein
quelle
1
Was ist, wenn eine Tabelle Daten enthält, die andere jedoch nicht? Willst du "und" oder "oder" dort?
Philip Kelley
1
Ich suche nach Datensätzen, auf die in keiner Tabelle verwiesen wird, also möchte ich UND. Ich werde die Frage klären.
Jeremy Stein
4
select *
from Common c
where not exists (select t1.commonid from table1 t1 where t1.commonid = c.commonid)
and not exists (select t2.commonid from table2 t2 where t2.commonid = c.commonid)
Patmortech
quelle
4

Ganz oben auf meinem Kopf ...

select c.commonID, t1.commonID, t2.commonID
from Common c
     left outer join Table1 t1 on t1.commonID = c.commonID
     left outer join Table2 t2 on t2.commonID = c.commonID
where t1.commonID is null 
     and t2.commonID is null

Ich habe einige Tests durchgeführt und hier waren meine Ergebnisse für die Antwort von @ patmortech und die Kommentare von @ rexem.

Wenn entweder Tabelle1 oder Tabelle2 nicht auf commonID ​​indiziert ist, erhalten Sie einen Tabellenscan, aber die Abfrage von @ patmortech ist immer noch doppelt so schnell (für eine 100K-Zeilen-Mastertabelle).

Wenn beide nicht auf commonID ​​indiziert sind, erhalten Sie zwei Tabellenscans und der Unterschied ist vernachlässigbar.

Wenn beide auf commonID ​​indiziert sind, wird die Abfrage "nicht vorhanden" in 1/3 der Zeit ausgeführt.

Austin Salonen
quelle
1
Das sollte ein UND in der where-Klausel sein. Ansonsten funktioniert das.
Jeremy Stein
1
geändert durch Ihren Kommentar. Das "oder" wählt Waisen in beiden Tabellen aus.
Austin Salonen
1
Das ist besser. Gibt es übrigens einen Grund, warum ich äußere Verknüpfungen anstelle der Unterabfrage verwenden sollte?
Jeremy Stein
3
Die Lesbarkeit steht an erster Stelle. Ich vermute, dass ein besserer Ausführungsplan generiert wird, aber ohne einen Abfrageplan kann ich nicht bestätigen.
Austin Salonen
2
Dieser Ansatz ist schlimmer als die Verwendung von NOT EXISTS - der Join führt dazu, dass mehr Zeilen abgerufen werden, als er benötigt, und dass die verglichenen Ergebnisse für die Spalten null sind. Und NOT EXISTS ist zum Booten besser lesbar.
OMG Ponys
3
SELECT T.common_id
  FROM Common T
       LEFT JOIN Table1 T1 ON T.common_id = T1.common_id
       LEFT JOIN Table2 T2 ON T.common_id = T2.common_id
 WHERE T1.common_id IS NULL
   AND T2.common_id IS NULL
Manji
quelle
1
Dieser Ansatz ist schlimmer als die Verwendung von NOT EXISTS - der Join führt dazu, dass mehr Zeilen abgerufen werden, als er benötigt, und dass die verglichenen Ergebnisse für die Spalten null sind. Es funktioniert, aber die Leistung ist nicht so gut - möglicherweise schlechter als bei Verwendung von IN mit korrelierten Unterabfragen.
OMG Ponys
3

Nehmen wir diese Werte für common_id an:

Common - 1
Table1 - 2
Table2 - 3, null

Wir möchten, dass die Zeile in Common zurückgegeben wird, da sie in keiner der anderen Tabellen vorhanden ist. Die Null wirft jedoch einen Schraubenschlüssel ein.

Mit diesen Werten entspricht die Abfrage:

select *
from Common
where 1 not in (2)
and 1 not in (3, null)

Das entspricht:

select *
from Common
where not (1=2)
and not (1=3 or 1=null)

Hier beginnt das Problem. Beim Vergleich mit einer Null ist die Antwort unbekannt . Die Abfrage reduziert sich also auf

select *
from Common
where not (false)
and not (false or unkown)

falsch oder unbekannt ist unbekannt:

select *
from Common
where true
and not (unknown)

wahr und nicht unbekannt ist auch unbekannt:

select *
from Common
where unknown

Die where-Bedingung gibt keine Datensätze zurück, bei denen das Ergebnis unbekannt ist, sodass wir keine Datensätze zurückerhalten.

Eine Möglichkeit, damit umzugehen, besteht darin, den vorhandenen Operator anstelle von in zu verwenden. Exists gibt niemals unbekannt zurück, da er eher mit Zeilen als mit Spalten arbeitet. (Eine Zeile existiert entweder oder nicht; keine dieser Null-Mehrdeutigkeiten auf Zeilenebene!)

select *
from Common
where not exists (select common_id from Table1 where common_id = Common.common_id)
and not exists (select common_id from Table2 where common_id = Common.common_id)
Jeremy Stein
quelle
2

das hat bei mir funktioniert :)

Wählen Sie * aus Allgemein

wo

common_id nicht in (wählen Sie ISNULL (common_id, 'Dummy-Daten') aus Tabelle 1)

und common_id nicht in (wählen Sie ISNULL (common_id, 'Dummy-Daten') aus Tabelle 2)

gebogen
quelle
@marlar, die Unterabfragen geben immer 1 oder 0 zurück, keine Werteliste. Wie werden die dort NOT INauftreten?
Istiaque Ahmed
0
select *,
(select COUNT(ID)  from ProductMaster where ProductMaster.CatID = CategoryMaster.ID) as coun 
from CategoryMaster
Donga Jayesh
quelle
0

Ich hatte ein Beispiel, in dem ich nachgeschlagen habe, und da eine Tabelle den Wert als Double und die andere als Zeichenfolge enthielt, stimmten sie nicht überein (oder stimmten nicht ohne Besetzung überein). Aber nur NICHT IN . Als SELECT ... IN ... funktionierte. Seltsam, aber ich dachte, ich würde es teilen, falls jemand anderes auf diese einfache Lösung stößt.

Ransems
quelle
0

Bitte folgen Sie dem folgenden Beispiel, um das obige Thema zu verstehen:

Sie können auch den folgenden Link besuchen, um Anti-Join zu erfahren

select department_name,department_id from hr.departments dep
where not exists 
    (select 1 from hr.employees emp
    where emp.department_id=dep.department_id
    )
order by dep.department_name;
DEPARTMENT_NAME DEPARTMENT_ID
Benefits    160
Construction    180
Contracting 190
.......

Aber wenn wir NOT INin diesem Fall verwenden, erhalten wir keine Daten.

select Department_name,department_id from hr.departments dep 
where department_id not in (select department_id from hr.employees );

keine Daten gefunden

Dies geschieht, wenn ( select department_id from hr.employees) einen Nullwert zurückgibt und die gesamte Abfrage als falsch ausgewertet wird. Wir können es sehen, wenn wir die SQL wie unten leicht ändern und Nullwerte mit der NVL-Funktion behandeln.

select Department_name,department_id from hr.departments dep 
where department_id not in (select NVL(department_id,0) from hr.employees )

Jetzt bekommen wir Daten:

DEPARTMENT_NAME DEPARTMENT_ID
Treasury    120
Corporate Tax   130
Control And Credit  140
Shareholder Services    150
Benefits    160
....

Wieder erhalten wir Daten, da wir den Nullwert mit der NVL-Funktion behandelt haben.

Rajesh Sarkar
quelle
SQl-Ergebnisse werden nicht in tabellarischer Form angezeigt.
Rajesh Sarkar