EXISTS (SELECT 1…) vs EXISTS (SELECT *…) Der eine oder andere?


37

Wenn ich in einer Tabelle nach einer Zeile suchen muss, schreibe ich in der Regel immer eine Bedingung wie:

SELECT a, b, c
  FROM a_table
 WHERE EXISTS
       (SELECT *  -- This is what I normally write
          FROM another_table
         WHERE another_table.b = a_table.b
       )

Einige andere Leute schreiben es so:

SELECT a, b, c
  FROM a_table
 WHERE EXISTS
       (SELECT 1   --- This nice '1' is what I have seen other people use
          FROM another_table
         WHERE another_table.b = a_table.b
       )

Wenn die Bedingung ist NOT EXISTSstatt EXISTS: In einigen Fällen könnte ich es schreibe mit ein LEFT JOINund einer zusätzlichen Bedingung (manchmal ein genannt antijoin ):

SELECT a, b, c
  FROM a_table
       LEFT JOIN another_table ON another_table.b = a_table.b
 WHERE another_table.primary_key IS NULL

Ich versuche es zu vermeiden, weil ich denke, dass die Bedeutung weniger klar ist, besonders wenn das, was Ihr primary_keyist, nicht so offensichtlich ist, oder wenn Ihr Primärschlüssel oder Ihre Join-Bedingung mehrspaltig ist (und Sie leicht eine der Spalten vergessen können). Manchmal behält man jedoch Code bei, der von jemand anderem geschrieben wurde ... und der ist einfach da.

  1. Gibt es einen Unterschied (außer Stil) zu verwenden, SELECT 1anstatt SELECT *?
    Gibt es einen Eckfall, in dem es sich nicht gleich verhält?

  2. Obwohl das, was ich geschrieben habe, (AFAIK) Standard-SQL ist: Gibt es einen solchen Unterschied für verschiedene Datenbanken / ältere Versionen?

  3. Gibt es einen Vorteil, wenn explizit ein Antijoin geschrieben wird?
    Behandeln zeitgenössische Planer / Optimierer dies anders als in der NOT EXISTSKlausel?


5
Beachten Sie, dass PostgreSQL Selects ohne Spalten unterstützt, sodass Sie einfach schreiben können EXISTS (SELECT FROM ...).
Rightfold

1
Ich habe vor ein paar Jahren fast die gleiche Frage zu SO gestellt: stackoverflow.com/questions/7710153/…
Erwin Brandstetter,

Antworten:


45

Nein, es gibt keinen Unterschied in der Effizienz zwischen (NOT) EXISTS (SELECT 1 ...)und (NOT) EXISTS (SELECT * ...)in allen wichtigen DBMS. Ich habe oft gesehen (NOT) EXISTS (SELECT NULL ...), wie man es benutzt.

In manchen kann man sogar schreiben (NOT) EXISTS (SELECT 1/0 ...)und das Ergebnis ist das gleiche - ohne irgendeinen (Division durch Null) Fehler, was beweist, dass der Ausdruck dort nicht einmal ausgewertet wird.


Über die LEFT JOIN / IS NULLAntijoin-Methode erfolgt eine Korrektur: Dies entspricht NOT EXISTS (SELECT ...).

In diesem Fall NOT EXISTSvsLEFT JOIN / IS NULLEs kann sein, dass Sie unterschiedliche Ausführungspläne erhalten. In MySQL zum Beispiel und meist in älteren Versionen (vor 5.7) wären die Pläne ziemlich ähnlich, aber nicht identisch. Die Optimierer anderer DBMS (SQL Server, Oracle, Postgres, DB2) sind meines Wissens mehr oder weniger in der Lage, diese beiden Methoden umzuschreiben und die gleichen Pläne für beide zu berücksichtigen. Es gibt jedoch keine solche Garantie und wenn Sie eine Optimierung durchführen, ist es gut, die Pläne von verschiedenen äquivalenten Umschreibungen zu überprüfen, da es Fälle geben kann, in denen die einzelnen Optimierer nicht umschreiben (z. B. komplexe Abfragen mit vielen Verknüpfungen und / oder abgeleiteten Tabellen). Unterabfragen innerhalb der Unterabfrage (Bedingungen aus mehreren Tabellen, zusammengesetzte Spalten, die in den Verbindungsbedingungen verwendet werden) oder die Optimierungsoptionen und -pläne werden von den verfügbaren Indizes, Einstellungen usw. unterschiedlich beeinflusst.

Beachten Sie auch, dass USINGdies nicht in allen DBMS verwendet werden kann (z. B. SQL Server). Das allgemeinere JOIN ... ONarbeitet überall.
Und den Spalten muss der Tabellenname / Alias ​​vorangestellt werden SELECT, um Fehler / Mehrdeutigkeiten bei Verknüpfungen zu vermeiden.
Normalerweise ziehe ich es auch vor, die verknüpfte Spalte in die IS NULLPrüfung einzubeziehen (obwohl die PK oder jede nicht nullfähige Spalte in Ordnung wäre, könnte dies aus Gründen der Effizienz hilfreich sein, wenn der Plan LEFT JOINeinen nicht gruppierten Index verwendet):

SELECT a_table.a, a_table.b, a_table.c
  FROM a_table
       LEFT JOIN another_table 
           ON another_table.b = a_table.b
 WHERE another_table.b IS NULL ;

Es gibt auch eine dritte Methode für Antijoins, die NOT INjedoch eine andere Semantik (und andere Ergebnisse!) Verwendet, wenn die Spalte der inneren Tabelle nullwertfähig ist. Es kann jedoch durch Ausschließen der Zeilen mit verwendet werden NULL, wodurch die Abfrage den vorherigen 2 Versionen entspricht:

SELECT a, b, c
  FROM a_table
 WHERE a_table.b NOT IN 
       (SELECT another_table.b
          FROM another_table
         WHERE another_table.b IS NOT NULL
       ) ;

Dies führt normalerweise auch zu ähnlichen Plänen in den meisten DBMS.


1
Bis zu den letzten Versionen von MySQL war die Leistung [NOT] IN (SELECT ...), obwohl gleichwertig, sehr schlecht. Vermeide es!
Rick James

3
Dies gilt nicht für PostgreSQL . SELECT *macht sicherlich mehr Arbeit. Ich würde der Einfachheit halber ratenSELECT 1
Evan Carroll

11

Es gibt eine Kategorie von Fällen , in denen SELECT 1und SELECT *nicht austauschbar sind - genauer gesagt, man wird immer vor allem in den Fällen , während der andere wird nicht akzeptiert.

Ich spreche von Fällen, in denen Sie überprüfen müssen, ob Zeilen einer gruppierten Menge vorhanden sind. Wenn die Tabelle TSpalten enthält C1und C2Sie überprüfen, ob Zeilengruppen vorhanden sind, die einer bestimmten Bedingung entsprechen, können Sie Folgendes verwenden SELECT 1:

EXISTS
(
  SELECT
    1
  FROM
    T
  GROUP BY
    C1
  HAVING
    AGG(C2) = SomeValue
)

aber Sie können nicht SELECT *auf die gleiche Weise verwenden.

Das ist nur ein syntaktischer Aspekt. Wenn beide Optionen syntaktisch akzeptiert werden, werden Sie höchstwahrscheinlich keinen Unterschied in Bezug auf die Leistung oder die zurückgegebenen Ergebnisse haben, wie in der anderen Antwort erläutert .

Zusätzliche Hinweise nach Kommentaren

Es scheint, dass nicht viele Datenbankprodukte diese Unterscheidung tatsächlich unterstützen. Produkte wie SQL Server, Oracle, MySQL und SQLite akzeptieren SELECT *die obige Abfrage gerne und fehlerfrei, was wahrscheinlich bedeutet, dass sie EXISTS SELECTauf besondere Weise behandeln.

PostgreSQL ist ein RDBMS, bei dem SELECT *möglicherweise ein Fehler auftritt, das jedoch in einigen Fällen noch funktioniert. Insbesondere, wenn Sie nach der PK gruppieren, SELECT *funktioniert dies einwandfrei, andernfalls schlägt die Meldung fehl:

FEHLER: Die Spalte "T.C2" muss in der GROUP BY-Klausel erscheinen oder in einer Aggregatfunktion verwendet werden


1
Gute Punkte, obwohl das nicht genau der Fall ist, um den ich mir Sorgen machte. Dieser zeigt einen konzeptionellen Unterschied. Denn wenn Sie GROUP BY, das Konzept von *ist bedeutungslos (oder zumindest nicht so klar).
Joanolo

5

Eine wohl interessante Möglichkeit, die EXISTSKlausel neu zu schreiben, die zu einer saubereren und vielleicht weniger irreführenden Abfrage führt, wäre zumindest in SQL Server:

SELECT a, b, c
  FROM a_table
 WHERE b = ANY
       (
          SELECT b
          FROM another_table
       );

Die Anti-Semi-Join-Version davon würde so aussehen:

SELECT a, b, c
  FROM a_table
 WHERE b <> ALL
       (
          SELECT b
          FROM another_table
       );

Beide sind in der Regel auf den gleichen Plan wie WHERE EXISTSoder optimiert WHERE NOT EXISTS, aber die Absicht ist unverkennbar, und Sie haben keine "seltsamen" 1oder *.

Interessanterweise sind die damit verbundenen Nullprüfungsprobleme NOT IN (...)problematisch <> ALL (...), wohingegen die NOT EXISTS (...)nicht unter diesem Problem leiden. Betrachten Sie die folgenden zwei Tabellen mit einer nullwertfähigen Spalte:

IF OBJECT_ID('tempdb..#t') IS NOT NULL
BEGIN
    DROP TABLE #t;
END;
CREATE TABLE #t 
(
    ID INT NOT NULL IDENTITY(1,1)
    , SomeValue INT NULL
);

IF OBJECT_ID('tempdb..#s') IS NOT NULL
BEGIN
    DROP TABLE #s;
END;
CREATE TABLE #s 
(
    ID INT NOT NULL IDENTITY(1,1)
    , SomeValue INT NULL
);

Wir fügen beiden Daten hinzu, wobei einige Zeilen übereinstimmen und andere nicht:

INSERT INTO #t (SomeValue) VALUES (1);
INSERT INTO #t (SomeValue) VALUES (2);
INSERT INTO #t (SomeValue) VALUES (3);
INSERT INTO #t (SomeValue) VALUES (NULL);

SELECT *
FROM #t;
+ -------- + ----------- +
| ID | SomeValue |
+ -------- + ----------- +
| 1 | 1 |
| 2 | 2 |
| 3 | 3 |
| 4 | NULL |
+ -------- + ----------- +
INSERT INTO #s (SomeValue) VALUES (1);
INSERT INTO #s (SomeValue) VALUES (2);
INSERT INTO #s (SomeValue) VALUES (NULL);
INSERT INTO #s (SomeValue) VALUES (4);

SELECT *
FROM #s;
+ -------- + ----------- +
| ID | SomeValue |
+ -------- + ----------- +
| 1 | 1 |
| 2 | 2 |
| 3 | NULL |
| 4 | 4 |
+ -------- + ----------- +

Die NOT IN (...)Abfrage:

SELECT *
FROM #t 
WHERE #t.SomeValue NOT IN (
    SELECT #s.SomeValue
    FROM #s 
    );

Hat folgenden Plan:

Bildbeschreibung hier eingeben

Die Abfrage gibt keine Zeilen zurück, da die Gleichheit aufgrund der NULL-Werte nicht bestätigt werden kann.

Diese Abfrage <> ALL (...)zeigt denselben Plan und gibt keine Zeilen zurück:

SELECT *
FROM #t 
WHERE #t.SomeValue <> ALL (
    SELECT #s.SomeValue
    FROM #s 
    );

Bildbeschreibung hier eingeben

Die Variante using NOT EXISTS (...), zeigt eine etwas andere Grundrissform und gibt Zeilen zurück:

SELECT *
FROM #t 
WHERE NOT EXISTS (
    SELECT 1
    FROM #s 
    WHERE #s.SomeValue = #t.SomeValue
    );

Der Plan:

Bildbeschreibung hier eingeben

Die Ergebnisse dieser Abfrage:

+ -------- + ----------- +
| ID | SomeValue |
+ -------- + ----------- +
| 3 | 3 |
| 4 | NULL |
+ -------- + ----------- +

Dies macht die Verwendung <> ALL (...)genauso anfällig für problematische Ergebnisse wie NOT IN (...).


3
Ich muss sagen, ich finde *es nicht seltsam: Ich lese EXISTS (SELECT * FROM t WHERE ...) AS there is a _row_ in table _t_ that.... Wie dem auch sei, ich habe gerne Alternativen und deine ist klar lesbar. Ein Zweifel / Vorbehalt: Wie wird es sich verhalten, wenn bes nullbar ist? [Ich hatte schlechte Erfahrungen und einige kurze Nächte, als ich versuchte, einen Irrtum herauszufinden, der durch einen Fehler verursacht wurde x IN (SELECT something_nullable FROM a_table)]
joanolo

EXISTS gibt an, ob eine Tabelle eine Zeile enthält und gibt true oder false zurück. EXISTS (SELECT x FROM (Werte (null)) ist wahr. IN ist = ANY & NOT IN ist <> ALL. Diese 4 nehmen eine RHS-Zeile mit NULL, um möglicherweise eine Übereinstimmung zu erzielen. (X) = ANY (Werte (null)) & (x) <> ALL (Werte (null)) sind unbekannt / null, aber EXISTS (Werte (null)) ist wahr. (IN & = ALLE haben die gleichen "Nullprüfungsprobleme, die mit NOT IN (...) [& ] <> ALL (...) ". JEDES & ALL iteriert ODER & UND. Es gibt jedoch nur" Probleme ", wenn Sie die Semantik nicht wie beabsichtigt organisieren.) Raten Sie nicht, diese für EXISTS zu verwenden. Sie sind irreführend , nicht "weniger irreführend"
philipxy

@philliprxy - Wenn ich mich irre, habe ich kein Problem damit, es zuzugeben. Fühlen Sie sich frei, Ihre eigene Antwort hinzuzufügen, wenn Sie Lust dazu haben.
Max Vernon

4

Der "Beweis", dass sie identisch sind (in MySQL), ist zu tun

EXPLAIN EXTENDED
    SELECT EXISTS ( SELECT * ... ) AS x;
SHOW WARNINGS;

dann mit wiederholen SELECT 1. In beiden Fällen zeigt die "erweiterte" Ausgabe, dass sie in umgewandelt wurde SELECT 1.

Ebenso COUNT(*)wird in verwandelt COUNT(0).

Noch etwas zu beachten: In den letzten Versionen wurden Optimierungsverbesserungen vorgenommen. Ein Vergleich mit EXISTSAnti-Joins kann sich lohnen . Ihre Version kann einen besseren Job mit einem gegen den anderen machen.


4

In einigen Datenbanken funktioniert diese Optimierung noch nicht. Wie zum Beispiel in PostgreSQL Ab Version 9.6 schlägt dies fehl.

SELECT *
FROM ( VALUES (1) ) AS g(x)
WHERE EXISTS (
  SELECT *
  FROM ( VALUES (1),(1) )
    AS t(x)
  WHERE g.x = t.x
  HAVING count(*) > 1
);

Und das wird gelingen.

SELECT *
FROM ( VALUES (1) ) AS g(x)
WHERE EXISTS (
  SELECT 1  -- This changed from the first query
  FROM ( VALUES (1),(1) )
    AS t(x)
  WHERE g.x = t.x
  HAVING count(*) > 1
);

Es schlägt fehl, weil das Folgende fehlschlägt, aber das bedeutet immer noch, dass es einen Unterschied gibt.

SELECT *
FROM ( VALUES (1),(1) ) AS t(x)
HAVING count(*) > 1;

Weitere Informationen zu dieser besonderen Eigenart und zum Verstoß gegen die Spezifikation finden Sie in meiner Antwort auf die Frage: Benötigt die SQL-Spezifikation eine GROUP BY in EXISTS ()?


Ein seltener Eckfall, vielleicht ein bisschen seltsam , aber ein
weiterer

-1

Ich habe immer verwendet select top 1 'x'(SQL Server)

Theoretisch select top 1 'x'wäre dies effizienter select *, da erstere vollständig wären, nachdem eine Konstante für das Vorhandensein einer qualifizierenden Zeile ausgewählt worden wäre, während letztere alles auswählen würde.

JEDOCH, obwohl es sehr früh relevant gewesen sein mag, hat die Optimierung den Unterschied in wahrscheinlich allen wichtigen RDBS irrelevant gemacht.


Macht Sinn. Das könnte einer der wenigen Fälle sein (oder gewesen sein könnten), in denen top nohne order byeine gute Idee sind.
Joanolo

3
"Theoretisch ..." Nein, theoretisch select top 1 'x'sollte es nicht effizienter sein als select *in einem ExistAusdruck. In der Praxis ist es möglicherweise effizienter, wenn der Optimierer nicht optimal funktioniert, theoretisch sind jedoch beide Ausdrücke gleichwertig.
miracle173

-4

IF EXISTS(SELECT TOP(1) 1 FROMist langfristig und plattformübergreifend eine bessere Angewohnheit, nur weil Sie sich nicht einmal Gedanken darüber machen müssen, wie gut oder schlecht Ihre aktuelle Plattform / Version ist. und SQL bewegt sich von TOP nzu parametrisierbar TOP(n). Dies sollte eine einmalige Lernfähigkeit sein.


3
Was meinst du mit "über Plattformen" ? TOPist nicht einmal gültiges SQL.
Ypercubeᵀᴹ

"SQL bewegt sich .." ist einfach falsch. Es gibt keine TOP (n)in "SQL" - der Standard-Abfragesprache. Unter T-SQL gibt es einen Dialekt, den Microsoft SQL Server verwendet.
a_horse_with_no_name

Das Tag in der ursprünglichen Frage lautet "SQL Server". Aber es ist in Ordnung, das, was ich gesagt habe, abzustimmen und zu bestreiten - es ist der Zweck dieser Site, ein einfaches Abstimmen zu ermöglichen. Wer bin ich, der mit langweiliger Liebe zum Detail auf Ihrer Parade regnet?
29.
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.