Wie funktionieren SQL EXISTS-Anweisungen?

86

Ich versuche SQL zu lernen und habe Schwierigkeiten, EXISTS-Anweisungen zu verstehen. Ich bin auf dieses Zitat über "existiert" gestoßen und verstehe etwas nicht:

Mit dem Operator exist kann Ihre Unterabfrage null, eine oder viele Zeilen zurückgeben, und die Bedingung prüft einfach, ob die Unterabfrage Zeilen zurückgegeben hat. Wenn Sie sich die select-Klausel der Unterabfrage ansehen, werden Sie feststellen, dass sie aus einem einzelnen Literal besteht (1). Da die Bedingung in der enthaltenen Abfrage nur wissen muss, wie viele Zeilen zurückgegeben wurden, sind die tatsächlichen Daten, die die Unterabfrage zurückgegeben hat, irrelevant.

Was ich nicht verstehe, ist, woher die äußere Abfrage weiß, welche Zeile die Unterabfrage überprüft? Beispielsweise:

SELECT *
  FROM suppliers
 WHERE EXISTS (select *
                 from orders
                where suppliers.supplier_id = orders.supplier_id);

Ich verstehe, dass wenn die ID aus der Lieferanten- und Auftragstabelle übereinstimmt, die Unterabfrage true zurückgibt und alle Spalten aus der übereinstimmenden Zeile in der Lieferantentabelle ausgegeben werden. Was ich nicht verstehe, ist, wie die Unterabfrage kommuniziert, welche bestimmte Zeile (sagen wir die Zeile mit der Lieferanten-ID 25) gedruckt werden soll, wenn nur ein wahres oder falsches zurückgegeben wird.

Es scheint mir, dass es keine Beziehung zwischen der äußeren Abfrage und der Unterabfrage gibt.

Dan
quelle

Antworten:

97

Denk darüber so:

SuppliersÜberprüfen Sie für 'jede' Zeile von , ob in der OrderTabelle eine Zeile 'vorhanden' ist , die die Bedingung erfüllt Suppliers.supplier_id(dies stammt aus der aktuellen 'Zeile' der äußeren Abfrage) = Orders.supplier_id. Wenn Sie die erste passende Zeile gefunden haben, halten Sie genau dort an - die WHERE EXISTSwurde erfüllt.

Die magische Verbindung zwischen der äußeren Abfrage und der Unterabfrage besteht in der Tatsache, dass Supplier_idfür jede ausgewertete Zeile die äußere Abfrage an die Unterabfrage übergeben wird.

Oder anders ausgedrückt: Die Unterabfrage wird für jede Tabellenzeile der äußeren Abfrage ausgeführt.

Es ist NICHT so, dass die Unterabfrage insgesamt ausgeführt wird und das "wahr / falsch" erhält und dann versucht, diese "wahr / falsch" -Bedingung mit der äußeren Abfrage abzugleichen.

Sojin
quelle
7
Vielen Dank! "Es ist NICHT wie eine Unterabfrage, die insgesamt ausgeführt wird und die 'wahr / falsch' erhält und dann versucht, diese 'wahr / falsch'-Bedingung mit der äußeren Abfrage abzugleichen." Das ist es, was es für mich wirklich geklärt hat. Ich denke immer wieder, dass Unterabfragen so funktionieren (und oft auch), aber was Sie gesagt haben, ist sinnvoll, weil die Unterabfrage auf der äußeren Abfrage beruht und daher einmal pro Zeile ausgeführt werden muss
Clarence Liu
32

Es scheint mir, dass es keine Beziehung zwischen der äußeren Abfrage und der Unterabfrage gibt.

Was macht Ihrer Meinung nach die WHERE-Klausel im EXISTS-Beispiel? Wie kommen Sie zu diesem Schluss, wenn die SUPPLIERS-Referenz nicht in den FROM- oder JOIN-Klauseln der EXISTS-Klausel enthalten ist?

EXISTS bewertet TRUE / FALSE und wird bei der ersten Übereinstimmung der Kriterien als TRUE beendet - aus diesem Grund kann es schneller sein als IN. Beachten Sie auch, dass die SELECT-Klausel in einem EXISTS ignoriert wird - IE:

SELECT s.*
  FROM SUPPLIERS s
 WHERE EXISTS (SELECT 1/0
                 FROM ORDERS o
                WHERE o.supplier_id = s.supplier_id)

... sollte eine Division durch Null Fehler treffen, wird es aber nicht. Die WHERE-Klausel ist das wichtigste Teil einer EXISTS-Klausel.

Beachten Sie auch, dass ein JOIN kein direkter Ersatz für EXISTS ist, da es doppelte übergeordnete Datensätze gibt, wenn dem übergeordneten Datensatz mehr als ein untergeordneter Datensatz zugeordnet ist.

OMG Ponys
quelle
1
Mir fehlt noch etwas. Wenn es beim ersten Match beendet wird, wie kommt es dann zu allen Ergebnissen, bei denen o.supplierid = s.supplierid ist? Würde es nicht stattdessen nur das erste Ergebnis ausgeben?
Dan
3
@Dan: Die EXISTSExits geben beim ersten Match TRUE zurück - da der Lieferant mindestens einmal in der Tabelle ORDERS vorhanden ist. Wenn Sie die Duplizierung der LIEFERANTEN-Daten sehen möchten, weil in ORDERS mehr als eine untergeordnete Beziehung vorhanden ist, müssen Sie JOIN verwenden. Die meisten möchten diese Duplizierung jedoch nicht, und das Ausführen von GROUP BY / DISTINCT kann zu einem zusätzlichen Aufwand für die Abfrage führen. EXISTSist effizienter als SELECT DISTINCT ... FROM SUPPLIERS JOIN ORDERS ...auf SQL Server, hat in letzter Zeit nicht auf Oracle oder MySQL getestet.
OMG Ponys
Ich hatte eine Frage, wird der Abgleich für jeden Datensatz durchgeführt, der in der äußeren Abfrage AUSGEWÄHLT ist. Wie in Abrufen von Bestellungen 5 Mal, wenn 5 Zeilen von Lieferanten ausgewählt wurden.
Rahul Kadukar
24

Sie können identische Ergebnisse erzeugen entweder mit JOIN, EXISTS, IN, oder INTERSECT:

SELECT s.supplier_id
FROM suppliers s
INNER JOIN (SELECT DISTINCT o.supplier_id FROM orders o) o
    ON o.supplier_id = s.supplier_id

SELECT s.supplier_id
FROM suppliers s
WHERE EXISTS (SELECT * FROM orders o WHERE o.supplier_id = s.supplier_id)

SELECT s.supplier_id 
FROM suppliers s 
WHERE s.supplier_id IN (SELECT o.supplier_id FROM orders o)

SELECT s.supplier_id
FROM suppliers s
INTERSECT
SELECT o.supplier_id
FROM orders o
Anthony Faull
quelle
1
tolle Antwort, aber auch bedenken, dass es besser ist, nicht zu verwenden, um Korrelationen zu vermeiden
Florian Fröhlich
1
Welche Abfrage wird Ihrer Meinung nach schneller ausgeführt, wenn Lieferanten 10 Millionen Zeilen und Bestellungen 100 Millionen Zeilen haben und warum?
Teja
7

Wenn Sie eine where-Klausel hatten, die so aussah:

WHERE id in (25,26,27) -- and so on

Sie können leicht verstehen, warum einige Zeilen zurückgegeben werden und andere nicht.

Wenn die where-Klausel so lautet:

WHERE EXISTS (select * from orders where suppliers.supplier_id = orders.supplier_id);

es bedeutet nur: Zeilen zurückgeben, die einen vorhandenen Datensatz in der Auftragstabelle mit derselben ID haben.

Menahem
quelle
2

Dies ist eine sehr gute Frage, deshalb habe ich beschlossen, einen sehr detaillierten Artikel zu diesem Thema in meinem Blog zu schreiben .

Datenbanktabellenmodell

Nehmen wir an, wir haben die folgenden zwei Tabellen in unserer Datenbank, die eine Eins-zu-Viele-Tabellenbeziehung bilden.

SQL EXISTS-Tabellen

Die studentTabelle ist die übergeordnete und die student_gradeuntergeordnete Tabelle, da sie eine Fremdschlüsselspalte student_id enthält, die auf die ID-Primärschlüsselspalte in der Schülertabelle verweist.

Das student tableenthält die folgenden zwei Datensätze:

| id | first_name | last_name | admission_score |
|----|------------|-----------|-----------------|
| 1  | Alice      | Smith     | 8.95            |
| 2  | Bob        | Johnson   | 8.75            |

In der student_gradeTabelle sind die Noten aufgeführt, die die Schüler erhalten haben:

| id | class_name | grade | student_id |
|----|------------|-------|------------|
| 1  | Math       | 10    | 1          |
| 2  | Math       | 9.5   | 1          |
| 3  | Math       | 9.75  | 1          |
| 4  | Science    | 9.5   | 1          |
| 5  | Science    | 9     | 1          |
| 6  | Science    | 9.25  | 1          |
| 7  | Math       | 8.5   | 2          |
| 8  | Math       | 9.5   | 2          |
| 9  | Math       | 9     | 2          |
| 10 | Science    | 10    | 2          |
| 11 | Science    | 9.4   | 2          |

SQL EXISTIERT

Nehmen wir an, wir möchten alle Schüler erreichen, die im Mathematikunterricht eine Note von 10 erhalten haben.

Wenn wir nur an der Studentenkennung interessiert sind, können wir eine Abfrage wie diese ausführen:

SELECT
    student_grade.student_id
FROM
    student_grade
WHERE
    student_grade.grade = 10 AND
    student_grade.class_name = 'Math'
ORDER BY
    student_grade.student_id

Die Anwendung ist jedoch daran interessiert, den vollständigen Namen von a anzuzeigen student, nicht nur den Bezeichner. Daher benötigen wir auch Informationen aus der studentTabelle.

Um die studentDatensätze mit einer Note von 10 in Mathematik zu filtern , können Sie den EXISTS SQL-Operator wie folgt verwenden:

SELECT
    id, first_name, last_name
FROM
    student
WHERE EXISTS (
    SELECT 1
    FROM
        student_grade
    WHERE
        student_grade.student_id = student.id AND
        student_grade.grade = 10 AND
        student_grade.class_name = 'Math'
)
ORDER BY id

Wenn Sie die obige Abfrage ausführen, sehen Sie, dass nur die Alice-Zeile ausgewählt ist:

| id | first_name | last_name |
|----|------------|-----------|
| 1  | Alice      | Smith     |

Die äußere Abfrage wählt die studentZeilenspalten aus, die an den Client zurückgegeben werden sollen. Die WHERE-Klausel verwendet jedoch den EXISTS-Operator mit einer zugehörigen inneren Unterabfrage.

Der EXISTS-Operator gibt true zurück, wenn die Unterabfrage mindestens einen Datensatz zurückgibt, und false, wenn keine Zeile ausgewählt ist. Das Datenbankmodul muss die Unterabfrage nicht vollständig ausführen. Wenn ein einzelner Datensatz übereinstimmt, gibt der EXISTS-Operator true zurück und die zugehörige andere Abfragezeile wird ausgewählt.

Die innere Unterabfrage ist korreliert, da die Spalte student_id der student_gradeTabelle mit der Spalte id der äußeren Schülertabelle abgeglichen wird.

Vlad Mihalcea
quelle
Was für eine großartige Antwort. Ich glaube, ich habe das Konzept nicht verstanden, weil ich ein falsches Beispiel verwendet habe. Funktioniert EXISTnur mit korrelierten Unterabfragen? Ich habe mit Abfragen herumgespielt, die nur 1 Tabelle enthielten SELECT id FROM student WHERE EXISTS (SELECT 1 FROM student WHERE student.id > 1). Ich weiß, was ich geschrieben habe, kann durch eine einfache WHERE-Abfrage erreicht werden, aber ich habe es nur verwendet, um EXISTS zu verstehen. Ich habe alle Reihen. Liegt es tatsächlich daran, dass ich keine korrelierte Unterabfrage verwendet habe? Vielen Dank.
Bowen Liu
Dies ist nur für korrelierte Unterabfragen sinnvoll, da Sie die Datensätze der äußeren Abfrage filtern möchten. In Ihrem Fall kann die innere Abfrage durch WHERE TRUE
Vlad Mihalcea
Danke Vlad. Das ist was ich dachte. Es ist nur eine seltsame Idee, die mir einfiel, als ich damit herumgespielt habe. Ich kannte das Konzept der korrelierten Unterabfrage ehrlich gesagt nicht. Und jetzt ist es viel sinnvoller, Zeilen der äußeren Abfrage mit der inneren Abfrage herauszufiltern.
Bowen Liu
0

EXISTS bedeutet, dass die Unterabfrage mindestens eine Zeile zurückgibt, das ist es wirklich. In diesem Fall handelt es sich um eine korrelierte Unterabfrage, da die Lieferanten-ID der äußeren Tabelle mit der Lieferanten-ID der inneren Tabelle überprüft wird. Diese Abfrage besagt im Endeffekt:

Alle Lieferanten AUSWÄHLEN Überprüfen Sie für jede Lieferanten-ID, ob für diesen Lieferanten eine Bestellung vorhanden ist. Wenn der Lieferant nicht in der Auftragstabelle vorhanden ist, entfernen Sie den Lieferanten aus den Ergebnissen. RÜCKGABE aller Lieferanten, die entsprechende Zeilen in der Auftragstabelle haben

In diesem Fall können Sie dasselbe mit einem INNER JOIN tun.

SELECT suppliers.* 
  FROM suppliers 
 INNER 
  JOIN orders 
    ON suppliers.supplier_id = orders.supplier_id;

Ponys Kommentar ist richtig. Sie müssen mit diesem Join eine Gruppierung durchführen oder je nach den von Ihnen benötigten Daten unterschiedliche auswählen.

David Fells
quelle
4
Der innere Join führt zu anderen Ergebnissen als EXISTS, wenn einem übergeordneten Datensatz mehr als ein untergeordneter Datensatz zugeordnet ist - sie sind nicht identisch.
OMG Ponys
Ich denke, meine Verwirrung könnte sein, dass ich gelesen habe, dass die Unterabfrage mit einem EXISTS wahr oder falsch zurückgibt; aber das kann nicht das einzige sein, was es zurückgibt, oder? Gibt die Unterabfrage auch alle "Lieferanten mit entsprechenden Zeilen in der Auftragstabelle" zurück? Aber wenn ja, wie gibt die EXISTS-Anweisung ein boolesches Ergebnis zurück? Alles, was ich in Lehrbüchern lese, besagt, dass es nur ein boolesches Ergebnis zurückgibt. Daher fällt es mir schwer, das Ergebnis des Codes mit dem abzugleichen, was mir mitgeteilt wird.
Dan
Lesen Sie EXISTS wie eine Funktion ... EXISTS (Ergebnismenge). Die EXISTS-Funktion würde dann true zurückgeben, wenn die Ergebnismenge Zeilen enthält, false, wenn sie leer ist. Das ist es im Grunde.
David Fells
3
@ Dan, bedenken Sie, dass EXISTS () für jede Quellzeile unabhängig logisch ausgewertet wird - es ist kein einzelner Wert für die gesamte Abfrage.
Arvo
-1

Was Sie beschreiben, ist eine sogenannte Abfrage mit einer korrelierten Unterabfrage .

(Im Allgemeinen) sollten Sie versuchen, dies zu vermeiden, indem Sie die Abfrage stattdessen mit einem Join schreiben:

SELECT suppliers.* 
FROM suppliers 
JOIN orders USING supplier_id
GROUP BY suppliers.supplier_id

Andernfalls wird die Unterabfrage für jede Zeile in der äußeren Abfrage ausgeführt.

Wouter van Nifterick
quelle
2
Diese beiden Lösungen sind nicht gleichwertig. Der JOIN liefert ein anderes Ergebnis als die EXISTS-Unterabfrage, wenn mehr als eine Zeile in ordersder Join-Bedingung vorhanden ist.
a_horse_with_no_name
1
Danke für die alternative Lösung. Aber schlagen Sie vor, dass ich, wenn eine Option zwischen korrelierter Unterabfrage und Join gegeben wird, mit Join gehen sollte, weil es effizienter ist?
sunny_dev