Join vs. Sub-Query


837

Ich bin ein MySQL-Benutzer der alten Schule und habe immer eine Unterabfrage vorgezogen JOIN. Aber heutzutage verwendet jeder eine Unterabfrage, und ich hasse es; Ich weiß nicht warum.

Mir fehlt das theoretische Wissen, um selbst zu beurteilen, ob es einen Unterschied gibt. Ist eine Unterabfrage so gut wie eine JOINund gibt es daher keinen Grund zur Sorge?


23
Unterabfragen sind manchmal großartig. Sie saugen in Bezug auf die Leistung in MySQL. Benutze sie nicht.
Runrig

8
Ich hatte immer den Eindruck, dass Unterabfragen implizit als Joins ausgeführt wurden, sofern diese in bestimmten DB-Technologien verfügbar waren.
Kezzer

18
Unterabfragen sind nicht immer schlecht, wenn Sie sich mit ziemlich großen Tabellen verbinden. Die bevorzugte Methode besteht darin, eine Unterauswahl aus dieser großen Tabelle vorzunehmen (die Anzahl der Zeilen zu begrenzen) und dann zu verbinden.
ovais.tariq

136
"Heutzutage verwendet jeder eine
Unterabfrage

3
Potenziell verwandt (obwohl viel spezifischer): stackoverflow.com/questions/141278/subqueries-vs-joins/…
Leigh Brenecki

Antworten:


191

Entnommen aus dem MySQL-Handbuch ( 13.2.10.11 Umschreiben von Unterabfragen als Joins ):

Ein LEFT [OUTER] JOIN kann schneller sein als eine entsprechende Unterabfrage, da der Server sie möglicherweise besser optimieren kann - eine Tatsache, die nicht nur für MySQL Server gilt.

Unterabfragen können also langsamer sein als LEFT [OUTER] JOIN, aber meiner Meinung nach ist ihre Stärke etwas besser lesbar .


45
@ user1735921 IMO kommt es darauf an ... Im Allgemeinen ist die Lesbarkeit des Codes sehr wichtig, da er für die spätere Verwaltung von großer Bedeutung ist ... Erinnern wir uns an die berühmte Aussage von Donald Knuth: "Vorzeitige Optimierung ist die Wurzel von allem böse (oder zumindest das meiste davon) in der Programmierung " . Natürlich gibt es Programmierbereiche, in denen Leistung an erster Stelle steht ... Idealerweise, wenn es einem gelingt, sich miteinander zu versöhnen :)
simhumileco

30
Bei komplexeren Abfragen sind Verknüpfungen viel einfacher zu lesen als Unterabfragen. Unterabfragen verwandeln sich in eine Schüssel mit Nudeln in meinem Kopf.
Zahra

6
@ user1735921 sicher, besonders wenn die Abfrage so kompliziert wird, dass sie das Falsche tut und Sie einen Tag damit verbringen, sie zu reparieren ... es gibt wie üblich ein Gleichgewicht dazwischen.
fabio.sussetto

6
@ user1735921 Nur wenn die Leistungssteigerungen die in Zukunft erforderliche Verlängerung der Wartungszeit wert sind
Joshua Schlichting

3
Meiner Meinung nach Joinund sub queryhat unterschiedliche Syntax, so dass Lesbarkeit wir nicht vergleichen können, beide haben eine höhere Lesbarkeit, solange Sie gut in SQL-Syntax sind. Leistung ist wichtiger.
Thavaprakash Swaminathan

842

Unterabfragen sind der logisch korrekte Weg, um Probleme des Formulars "Fakten von A abrufen, abhängig von Fakten von B" zu lösen. In solchen Fällen ist es logischer, B in eine Unterabfrage zu stecken, als einen Join durchzuführen. In praktischer Hinsicht ist dies auch sicherer, da Sie nicht vorsichtig sein müssen, wenn Sie aufgrund mehrerer Spiele gegen B doppelte Fakten von A erhalten.

In der Praxis kommt die Antwort jedoch normalerweise auf die Leistung an. Einige Optimierer saugen Zitronen, wenn sie einen Join gegen eine Unterabfrage erhalten, und andere saugen Zitronen in die andere Richtung. Dies ist optimiererspezifisch, DBMS-version-spezifisch und abfragespezifisch.

Historisch gesehen gewinnen explizite Verknüpfungen normalerweise, daher ist die etablierte Weisheit, dass Verknüpfungen besser sind, aber Optimierer werden immer besser. Daher schreibe ich Abfragen lieber zuerst auf logisch kohärente Weise und restrukturiere sie dann, wenn Leistungsbeschränkungen dies rechtfertigen.


105
Gute Antwort. Ich möchte auch hinzufügen, dass Entwickler (insbesondere Amateurentwickler) SQL nicht immer beherrschen.
Álvaro González

4
+1 Auf der Suche nach einer logischen Erklärung für dieses Problem für eine lange Zeit, ist dies nur eine Antwort, die mir logisch erscheint
Ali Umair

1
@Marcelo Cantos, Könnten Sie bitte ein Beispiel für Ihre Aussage geben: "Es ist auch im praktischen Sinne sicherer, da Sie nicht vorsichtig sein müssen, wenn Sie aufgrund mehrerer Spiele gegen B doppelte Fakten von A erhalten." Ich fand das sehr aufschlussreich und doch etwas zu abstrakt. Vielen Dank.
Jinghui Niu

6
@JinghuiNiu Kunden, die teure Artikel gekauft haben : select custid from cust join bought using (custid) where price > 500. Wenn ein Kunde mehrere teure Artikel gekauft hat, erhalten Sie Double-Ups. Um dies zu beheben , select custid from cust where exists (select * from bought where custid = cust.custid and price > 500). Sie könnten select distinct …stattdessen verwenden, aber es ist oft mehr Arbeit, entweder für den Optimierer oder den Evaluator.
Marcelo Cantos

1
@MatTheWhale Ja, ich habe eine vereinfachte Antwort verwendet, weil ich faul war. In einem realen Szenario würden Sie mehr Spalten als nur custid von cust ziehen.
Marcelo Cantos

357

In den meisten Fällen sind JOINs schneller als Unterabfragen und es ist sehr selten, dass eine Unterabfrage schneller ist.

In JOINs kann RDBMS einen Ausführungsplan erstellen, der für Ihre Abfrage besser geeignet ist, und vorhersagen, welche Daten zur Verarbeitung geladen werden sollen, und Zeit sparen, im Gegensatz zu der Unterabfrage, bei der alle Abfragen ausgeführt und alle Daten für die Verarbeitung geladen werden .

Das Gute an Unterabfragen ist, dass sie besser lesbar sind als JOINs: Deshalb bevorzugen die meisten neuen SQL-Benutzer sie. es ist der einfache Weg; Aber wenn es um Leistung geht, sind JOINS in den meisten Fällen besser, obwohl sie auch nicht schwer zu lesen sind.


14
Ja, die meisten Datenbanken enthalten es daher als Optimierungsschritt, um Unterabfragen in Joins zu konvertieren, wenn Ihre Abfrage analysiert wird.
Cine

16
Diese Antwort ist für die gestellte Frage etwas zu vereinfacht. Wie Sie sagen: Bestimmte Unterabfragen sind in Ordnung und bestimmte nicht. Die Antwort hilft nicht wirklich, die beiden zu unterscheiden. (Auch das "sehr seltene" hängt wirklich von Ihren Daten / App ab).
Unvernunft

21
Können Sie einen Ihrer Punkte anhand von Dokumentationsreferenzen oder Testergebnissen belegen?
Uğur Gümüşhan

62
Ich habe sehr gute Erfahrungen mit Unterabfragen gemacht, die einen Rückverweis auf die obere Abfrage enthalten, insbesondere wenn es um Zeilenzahlen über 100.000 geht. Die Sache scheint die Speichernutzung und das Paging in die Auslagerungsdatei zu sein. Ein Join würde eine sehr große Datenmenge erzeugen, die möglicherweise nicht in den Speicher passt und in die Auslagerungsdatei ausgelagert werden muss. Wann immer dies der Fall ist, sind die Abfragezeiten von kleinen Unterauswahlen wie select * from a where a.x = (select b.x form b where b.id = a.id)im Vergleich zu einem Join extrem klein. Dies ist ein sehr spezifisches Problem, aber in einigen Fällen dauert es Stunden bis Minuten.
Zuloo

13
Ich habe Erfahrung mit Oracle und kann sagen, dass Unterabfragen in großen Tabellen viel besser sind, wenn Sie sie nicht filtern oder sortieren.
Amir Pashazadeh

130

Verwenden Sie EXPLAIN, um zu sehen, wie Ihre Datenbank die Abfrage für Ihre Daten ausführt. In dieser Antwort steckt ein riesiges "es kommt darauf an" ...

PostgreSQL kann eine Unterabfrage in einen Join oder einen Join in eine Unterabfrage umschreiben, wenn es glaubt, dass eine schneller als die andere ist. Es hängt alles von den Daten, Indizes, Korrelationen, Datenmengen, Abfragen usw. ab.


6
Dies ist genau der Grund, warum postgresql so gut und nützlich ist, dass es versteht, was das Ziel ist, und eine Abfrage basierend auf dem, was es für besser hält, behebt. postgresql weiß sehr gut, wie man seine Daten betrachtet
WojonsTech

heww. Ich denke, ich muss nicht viele Anfragen für mich neu schreiben! postgresql für den Sieg.
Daniel Shin

77

Im Jahr 2010 wäre ich dem Autor dieser Fragen beigetreten und hätte stark dafür gestimmt JOIN, aber mit viel mehr Erfahrung (insbesondere in MySQL) kann ich sagen: Ja, Unterabfragen können besser sein. Ich habe hier mehrere Antworten gelesen. Einige der angegebenen Unterabfragen sind schneller, aber es fehlte eine gute Erklärung. Ich hoffe, ich kann einem diese (sehr) späte Antwort geben:

Lassen Sie mich zunächst das Wichtigste sagen: Es gibt verschiedene Formen von Unterabfragen

Und die zweite wichtige Aussage: Größe ist wichtig

Wenn Sie Unterabfragen verwenden, sollten Sie wissen, wie der DB-Server die Unterabfrage ausführt. Besonders wenn die Unterabfrage einmal oder für jede Zeile ausgewertet wird! Auf der anderen Seite kann ein moderner DB-Server viel optimieren. In einigen Fällen hilft eine Unterabfrage bei der Optimierung einer Abfrage, aber eine neuere Version des DB-Servers kann die Optimierung überflüssig machen.

Unterabfragen in Auswahlfeldern

SELECT moo, (SELECT roger FROM wilco WHERE moo = me) AS bar FROM foo

Beachten Sie, dass für jede resultierende Zeile aus eine Unterabfrage ausgeführt wird foo.
Vermeiden Sie dies wenn möglich; Dies kann Ihre Abfrage bei großen Datenmengen drastisch verlangsamen. Wenn die Unterabfrage jedoch keinen Verweis darauf foohat, kann sie vom DB-Server als statischer Inhalt optimiert und nur einmal ausgewertet werden.

Unterabfragen in der Where-Anweisung

SELECT moo FROM foo WHERE bar = (SELECT roger FROM wilco WHERE moo = me)

Wenn Sie Glück haben, optimiert die DB dies intern in eine JOIN. Wenn nicht, wird Ihre Abfrage bei großen Datenmengen sehr, sehr langsam, da die Unterabfrage für jede Zeile ausgeführt wird foo, nicht nur für die Ergebnisse wie beim Auswahltyp.

Unterabfragen in der Join-Anweisung

SELECT moo, bar 
  FROM foo 
    LEFT JOIN (
      SELECT MIN(bar), me FROM wilco GROUP BY me
    ) ON moo = me

Das ist interessant. Wir kombinieren JOINmit einer Unterabfrage. Und hier bekommen wir die wahre Stärke von Unterabfragen. Stellen Sie sich einen Datensatz mit Millionen von Zeilen vor, wilcoaber nur wenigen unterschiedlichen me. Anstatt sich gegen einen riesigen Tisch anzumelden, haben wir jetzt einen kleineren temporären Tisch, gegen den wir uns anmelden können. Dies kann je nach Datenbankgröße zu viel schnelleren Abfragen führen. Sie können den gleichen Effekt mit CREATE TEMPORARY TABLE ...und erzielen INSERT INTO ... SELECT ..., was möglicherweise zu einer besseren Lesbarkeit bei sehr komplexen Abfragen führt (Sie können jedoch Datensätze in einer wiederholbaren Leseisolationsstufe sperren).

Verschachtelte Unterabfragen

SELECT moo, bar
  FROM (
    SELECT moo, CONCAT(roger, wilco) AS bar
      FROM foo
      GROUP BY moo
      HAVING bar LIKE 'SpaceQ%'
  ) AS temp_foo
  ORDER BY bar

Sie können Unterabfragen in mehreren Ebenen verschachteln. Dies kann bei großen Datenmengen hilfreich sein, wenn Sie die Ergebnisse gruppieren oder sortieren müssen. Normalerweise erstellt der DB-Server hierfür eine temporäre Tabelle, aber manchmal müssen Sie nicht die gesamte Tabelle sortieren, sondern nur die Ergebnismenge. Dies kann je nach Größe der Tabelle zu einer viel besseren Leistung führen.

Fazit

Unterabfragen sind kein Ersatz für a JOINund Sie sollten sie nicht so verwenden (obwohl möglich). Meiner bescheidenen Meinung nach ist die korrekte Verwendung einer Unterabfrage die Verwendung als schneller Ersatz für CREATE TEMPORARY TABLE .... Eine gute Unterabfrage reduziert ein Dataset auf eine Weise, die Sie in einer ONAnweisung von a nicht erreichen können JOIN. Wenn eine Unterabfrage eines der Schlüsselwörter hat GROUP BYoder DISTINCTsich vorzugsweise nicht in den Auswahlfeldern oder in der where-Anweisung befindet, kann dies die Leistung erheblich verbessern.


3
Für Sub-queries in the Join-statement: (1) Das Generieren einer abgeleiteten Tabelle aus der Unterabfrage selbst kann sehr lange dauern. (2) Die resultierende abgeleitete Tabelle wird nicht indiziert. Diese beiden allein könnten die SQL erheblich verlangsamen.
JXC

@jxc Ich kann nur für MySQL sprechen (1) Dort gibt es eine temporäre Tabelle ähnlich einem Join. Die Zeit hängt von der Datenmenge ab. Wenn Sie die Daten mit einer Unterabfrage nicht reduzieren können, verwenden Sie einen Join. (2) Dies ist richtig, es hängt von dem Faktor ab, mit dem Sie die Daten in der temporären Tabelle reduzieren können. Ich hatte Fälle aus der realen Welt, in denen ich die Join-Größe von einigen Millionen auf einige Hundert reduzieren und die Abfragezeit von mehreren Sekunden (bei voller Indexnutzung) auf eine Viertelsekunde mit einer Unterabfrage reduzieren konnte.
Trendfischer

IMO: (1) Eine solche temporäre Tabelle (abgeleitete Tabelle) wird nicht materialisiert. Daher muss jedes Mal, wenn Sie SQL ausführen, die temporäre Tabelle neu erstellt werden. Dies kann sehr kostspielig sein und einen echten Flaschenhals darstellen (dh eine Gruppe mit mehreren Millionen ausführen) Anzahl von Datensätzen) (2) Selbst wenn Sie die Größe der temporären Tabelle auf 10Datensätze reduzieren können , da kein Index vorhanden ist, bedeutet dies möglicherweise, dass beim Verbinden anderer Tabellen möglicherweise 9-mal mehr Datensätze als ohne temporäre Tabelle abgefragt werden. Übrigens hatte ich dieses Problem schon einmal mit meiner Datenbank (MySQL). In meinem Fall könnte die Verwendung von Unterabfragen in SELECT listviel schneller sein.
JXC

@jxc Ich bezweifle nicht, dass es viele Beispiele gibt, bei denen die Verwendung einer Unterabfrage weniger optimal ist. Als bewährte Methode sollten Sie EXPLAINvor der Optimierung eine Abfrage verwenden. Mit dem alten set profiling=1konnte man leicht erkennen, ob ein temporärer Tisch ein Engpass ist. Und selbst ein Index benötigt Verarbeitungszeit. B-Trees optimieren die Abfrage nach Datensätzen, aber eine Tabelle mit 10 Datensätzen kann viel schneller sein als ein Index für Millionen von Datensätzen. Dies hängt jedoch von mehreren Faktoren wie Feldgrößen und -typen ab.
Trendfischer

1
Ich habe Ihre Erklärung wirklich genossen. Vielen Dank.
unpaarig gut

43

Um die beiden zuerst zu vergleichen, sollten Sie zunächst Abfragen mit Unterabfragen unterscheiden, um:

  1. Eine Klasse von Unterabfragen, für die immer entsprechende äquivalente Abfragen mit Joins geschrieben wurden
  2. Eine Klasse von Unterabfragen, die nicht mithilfe von Joins neu geschrieben werden können

Für die erste Klasse von Abfragen sieht ein gutes RDBMS Verknüpfungen und Unterabfragen als gleichwertig und erzeugt dieselben Abfragepläne.

Heutzutage macht das sogar MySQL.

Manchmal ist dies jedoch nicht der Fall, aber dies bedeutet nicht, dass Joins immer gewinnen. Ich hatte Fälle, in denen Unterabfragen in MySQL die Leistung verbesserten. (Wenn beispielsweise etwas den MySQL-Planer daran hindert, die Kosten korrekt zu schätzen, und der Planer die Join-Variante und die Unterabfrage-Variante nicht als gleich ansieht, können Unterabfragen die Joins übertreffen, indem sie einen bestimmten Pfad erzwingen.)

Die Schlussfolgerung ist, dass Sie Ihre Abfragen sowohl für Join- als auch für Unterabfragevarianten testen sollten, wenn Sie sicher sein möchten, welche Variante eine bessere Leistung erbringt.

Für die zweite Klasse macht der Vergleich keinen Sinn, da diese Abfragen nicht mithilfe von Joins neu geschrieben werden können. In diesen Fällen sind Unterabfragen eine natürliche Methode, um die erforderlichen Aufgaben auszuführen, und Sie sollten sie nicht diskriminieren.


1
Können Sie ein Beispiel für eine Abfrage angeben, die mit Unterabfragen geschrieben wurde, die nicht in Joins konvertiert werden können (zweite Klasse, wie Sie sie nennen)?
Zahra

24

Ich denke, was in den zitierten Antworten unterbetont wurde, ist das Problem von Duplikaten und problematischen Ergebnissen, die sich aus bestimmten (Anwendungs-) Fällen ergeben können.

(obwohl Marcelo Cantos es erwähnt)

Ich werde das Beispiel aus Stanfords Lagunita-Kursen zu SQL zitieren.

Schülertisch

+------+--------+------+--------+
| sID  | sName  | GPA  | sizeHS |
+------+--------+------+--------+
|  123 | Amy    |  3.9 |   1000 |
|  234 | Bob    |  3.6 |   1500 |
|  345 | Craig  |  3.5 |    500 |
|  456 | Doris  |  3.9 |   1000 |
|  567 | Edward |  2.9 |   2000 |
|  678 | Fay    |  3.8 |    200 |
|  789 | Gary   |  3.4 |    800 |
|  987 | Helen  |  3.7 |    800 |
|  876 | Irene  |  3.9 |    400 |
|  765 | Jay    |  2.9 |   1500 |
|  654 | Amy    |  3.9 |   1000 |
|  543 | Craig  |  3.4 |   2000 |
+------+--------+------+--------+

Tabelle anwenden

(Bewerbungen an bestimmten Universitäten und Hauptfächern)

+------+----------+----------------+----------+
| sID  | cName    | major          | decision |
+------+----------+----------------+----------+
|  123 | Stanford | CS             | Y        |
|  123 | Stanford | EE             | N        |
|  123 | Berkeley | CS             | Y        |
|  123 | Cornell  | EE             | Y        |
|  234 | Berkeley | biology        | N        |
|  345 | MIT      | bioengineering | Y        |
|  345 | Cornell  | bioengineering | N        |
|  345 | Cornell  | CS             | Y        |
|  345 | Cornell  | EE             | N        |
|  678 | Stanford | history        | Y        |
|  987 | Stanford | CS             | Y        |
|  987 | Berkeley | CS             | Y        |
|  876 | Stanford | CS             | N        |
|  876 | MIT      | biology        | Y        |
|  876 | MIT      | marine biology | N        |
|  765 | Stanford | history        | Y        |
|  765 | Cornell  | history        | N        |
|  765 | Cornell  | psychology     | Y        |
|  543 | MIT      | CS             | N        |
+------+----------+----------------+----------+

Versuchen wir, die GPA-Ergebnisse für Studenten zu finden, die sich für ein CSHauptfach beworben haben (unabhängig von der Universität).

Verwenden einer Unterabfrage:

select GPA from Student where sID in (select sID from Apply where major = 'CS');

+------+
| GPA  |
+------+
|  3.9 |
|  3.5 |
|  3.7 |
|  3.9 |
|  3.4 |
+------+

Der Durchschnittswert für diese Ergebnismenge ist:

select avg(GPA) from Student where sID in (select sID from Apply where major = 'CS');

+--------------------+
| avg(GPA)           |
+--------------------+
| 3.6800000000000006 |
+--------------------+

Verwenden eines Joins:

select GPA from Student, Apply where Student.sID = Apply.sID and Apply.major = 'CS';

+------+
| GPA  |
+------+
|  3.9 |
|  3.9 |
|  3.5 |
|  3.7 |
|  3.7 |
|  3.9 |
|  3.4 |
+------+

Durchschnittswert für diese Ergebnismenge:

select avg(GPA) from Student, Apply where Student.sID = Apply.sID and Apply.major = 'CS';

+-------------------+
| avg(GPA)          |
+-------------------+
| 3.714285714285714 |
+-------------------+

Es ist offensichtlich, dass der zweite Versuch in unserem Anwendungsfall zu irreführenden Ergebnissen führt, da für die Berechnung des Durchschnittswerts Duplikate gezählt werden. Es ist auch offensichtlich, dass die Verwendung von distinctmit der join-basierten Anweisung das Problem nicht beseitigt, da fälschlicherweise eines von drei Vorkommen der 3.9Punktzahl beibehalten wird. Der richtige Fall besteht darin, ZWEI (2) Vorkommen der 3.9Punktzahl zu berücksichtigen, vorausgesetzt , wir haben tatsächlich ZWEI (2) Schüler mit dieser Punktzahl, die unseren Abfragekriterien entsprechen.

In einigen Fällen scheint eine Unterabfrage neben Leistungsproblemen der sicherste Weg zu sein.


Ich denke, Sie können hier keine Unterabfrage verwenden. Dies ist kein Fall, in dem Sie beide logisch verwenden können, aber man gibt aufgrund der technischen Implementierung eine falsche Antwort. Dies ist ein Fall, in dem Sie KEINE Unterabfrage verwenden können, da ein Schüler, der nicht zu CS gehört, 3,9 Punkte erzielen kann, was in der IN-Liste der Punkte enthalten ist. Der Kontext von CS geht verloren, sobald eine Unterabfrage ausgeführt wird. Dies ist logischerweise nicht das, was wir wollen. Dies ist also kein gutes Beispiel, wo beides verwendet werden kann. Die Verwendung von Unterabfragen ist für diesen Anwendungsfall konzeptionell / logisch falsch, auch wenn sie zum Glück das richtige Ergebnis für einen anderen Datensatz liefert.
Saurabh Patil

22

In der MSDN-Dokumentation für SQL Server heißt es

Viele Transact-SQL-Anweisungen, die Unterabfragen enthalten, können alternativ als Verknüpfungen formuliert werden. Andere Fragen können nur mit Unterabfragen gestellt werden. In Transact-SQL gibt es normalerweise keinen Leistungsunterschied zwischen einer Anweisung, die eine Unterabfrage enthält, und einer semantisch äquivalenten Version, die dies nicht tut. In einigen Fällen, in denen die Existenz überprüft werden muss, führt ein Join jedoch zu einer besseren Leistung. Andernfalls muss die verschachtelte Abfrage für jedes Ergebnis der äußeren Abfrage verarbeitet werden, um die Beseitigung von Duplikaten sicherzustellen. In solchen Fällen würde ein Join-Ansatz zu besseren Ergebnissen führen.

Also, wenn Sie so etwas brauchen

select * from t1 where exists select * from t2 where t2.parent=t1.id

Versuchen Sie stattdessen, join zu verwenden. In anderen Fällen macht es keinen Unterschied.

Ich sage: Das Erstellen von Funktionen für Unterabfragen beseitigt das Problem des Cluttters und ermöglicht es Ihnen, zusätzliche Logik für Unterabfragen zu implementieren. Ich empfehle daher, wann immer möglich Funktionen für Unterabfragen zu erstellen.

Unordnung im Code ist ein großes Problem, und die Industrie arbeitet seit Jahrzehnten daran, es zu vermeiden.


9
Das Ersetzen von Unterabfragen durch Funktionen ist in Bezug auf die Leistung in einigen RDBMS (z. B. Oracle) eine sehr schlechte Idee. Daher würde ich genau das Gegenteil empfehlen: Verwenden Sie nach Möglichkeit Unterabfragen / Verknüpfungen anstelle von Funktionen.
Frank Schmitt

3
@FrankSchmitt Bitte unterstützen Sie Ihre Argumentation mit Referenzen.
Uğur Gümüşhan

2
Es gibt auch Fälle, in denen Sie eine Unterabfrage anstelle eines Joins verwenden sollten, selbst wenn Sie auf Existenz prüfen: wenn Sie nach prüfen NOT EXISTS. A NOT EXISTSgewinnt LEFT OUTER JOIN aus verschiedenen Gründen gegen a: Leistung, Ausfallsicherheit (bei nulierbaren Spalten) und Lesbarkeit. sqlperformance.com/2012/12/t-sql-queries/left-anti-semi-join
Tim Schmelter

16

Führen Sie eine sehr große Datenbank von einem alten Mambo-CMS aus:

SELECT id, alias
FROM
  mos_categories
WHERE
  id IN (
    SELECT
      DISTINCT catid
    FROM mos_content
  );

0 Sekunden

SELECT
  DISTINCT mos_content.catid,
  mos_categories.alias
FROM
  mos_content, mos_categories
WHERE
  mos_content.catid = mos_categories.id;

~ 3 Sekunden

Eine EXPLAIN zeigt, dass sie genau die gleiche Anzahl von Zeilen untersuchen, aber eine dauert 3 Sekunden und eine ist fast augenblicklich. Moral der Geschichte? Wenn Leistung wichtig ist (wann nicht?), Probieren Sie es auf verschiedene Arten aus und finden Sie heraus, welche am schnellsten ist.

Und...

SELECT
  DISTINCT mos_categories.id,
  mos_categories.alias
FROM
  mos_content, mos_categories
WHERE
  mos_content.catid = mos_categories.id;

0 Sekunden

Wieder die gleichen Ergebnisse, die gleiche Anzahl der untersuchten Zeilen. Ich vermute, dass DISTINCT mos_content.catid viel länger braucht, um herauszufinden, als DISTINCT mos_categories.id.


1
Ich möchte mehr darüber wissen, worauf Sie in der letzten Zeile hinweisen möchten. "Ich vermute, dass DISTINCT mos_content.catid viel länger braucht, um herauszufinden, als DISTINCT mos_categories.id." . Wollen Sie damit sagen, dass eine ID nur benannt werden sollte idund nicht so catid? Der Versuch, meine Datenbankzugriffe zu optimieren, und Ihre Erkenntnisse könnten helfen.
bool.dev

2
Die Verwendung von SQL IN ist in diesem Fall eine schlechte Praxis und beweist nichts.
Uğur Gümüşhan

15

Nach meiner Beobachtung wie in zwei Fällen funktioniert der Join schnell, wenn eine Tabelle weniger als 100.000 Datensätze enthält.

Wenn eine Tabelle jedoch mehr als 100.000 Datensätze enthält, ist eine Unterabfrage das beste Ergebnis.

Ich habe eine Tabelle mit 500.000 Datensätzen, die ich unter der Abfrage erstellt habe, und die Ergebniszeit ist wie folgt

SELECT * 
FROM crv.workorder_details wd 
inner join  crv.workorder wr on wr.workorder_id = wd.workorder_id;

Ergebnis: 13,3 Sekunden

select * 
from crv.workorder_details 
where workorder_id in (select workorder_id from crv.workorder)

Ergebnis: 1,65 Sekunden


Ich bin damit einverstanden, dass manchmal das Unterbrechen der Abfrage auch funktioniert. Wenn Sie über Millionen Datensätze verfügen, möchten Sie keine Joins verwenden, da diese für immer dauern. Behandeln Sie es lieber im Code und die Zuordnung im Code ist besser.
user1735921

1
Wenn Ihre Verknüpfungen nicht schnell genug funktionieren, fehlt möglicherweise ein Index. Query Analyzer kann beim Vergleich der tatsächlichen Leistung sehr hilfreich sein.
digital.aaron

Ich stimme Ajay Gajera zu, das habe ich selbst gesehen.
user1735921

14
Wie ist es sinnvoll, die Leistung von zwei Abfragen zu vergleichen, die unterschiedliche Ergebnisse liefern?
Paul Spiegel

Ja, das sind verschiedene Abfragen, aber das gleiche Ergebnis wird zurückgegeben
König Neo

12

Unterabfragen werden im Allgemeinen verwendet, um eine einzelne Zeile als atomaren Wert zurückzugeben. Sie können jedoch verwendet werden, um Werte mit mehreren Zeilen mit dem Schlüsselwort IN zu vergleichen. Sie sind an nahezu jedem wichtigen Punkt in einer SQL-Anweisung zulässig, einschließlich der Zielliste, der WHERE-Klausel usw. Eine einfache Unterabfrage kann als Suchbedingung verwendet werden. Zum Beispiel zwischen zwei Tabellen:

   SELECT title FROM books WHERE author_id = (SELECT id FROM authors WHERE last_name = 'Bar' AND first_name = 'Foo');

Beachten Sie, dass für die Verwendung eines Normalwertoperators für die Ergebnisse einer Unterabfrage nur ein Feld zurückgegeben werden muss. Wenn Sie prüfen möchten, ob ein einzelner Wert in einer Reihe anderer Werte vorhanden ist, verwenden Sie IN:

   SELECT title FROM books WHERE author_id IN (SELECT id FROM authors WHERE last_name ~ '^[A-E]');

Dies unterscheidet sich offensichtlich von einem LEFT-JOIN, bei dem Sie nur Inhalte aus Tabelle A und B verknüpfen möchten, auch wenn die Verknüpfungsbedingung keinen passenden Datensatz in Tabelle B usw. findet.

Wenn Sie sich nur Sorgen um die Geschwindigkeit machen, müssen Sie Ihre Datenbank überprüfen und eine gute Abfrage schreiben, um festzustellen, ob es einen signifikanten Leistungsunterschied gibt.


11

MySQL-Version: 5.5.28-0ubuntu0.12.04.2-log

Ich hatte auch den Eindruck, dass JOIN in MySQL immer besser ist als eine Unterabfrage, aber EXPLAIN ist ein besserer Weg, um ein Urteil zu fällen. Hier ist ein Beispiel, in dem Unterabfragen besser funktionieren als JOINs.

Hier ist meine Anfrage mit 3 Unterabfragen:

EXPLAIN SELECT vrl.list_id,vrl.ontology_id,vrl.position,l.name AS list_name, vrlih.position AS previous_position, vrl.moved_date 
FROM `vote-ranked-listory` vrl 
INNER JOIN lists l ON l.list_id = vrl.list_id 
INNER JOIN `vote-ranked-list-item-history` vrlih ON vrl.list_id = vrlih.list_id AND vrl.ontology_id=vrlih.ontology_id AND vrlih.type='PREVIOUS_POSITION' 
INNER JOIN list_burial_state lbs ON lbs.list_id = vrl.list_id AND lbs.burial_score < 0.5 
WHERE vrl.position <= 15 AND l.status='ACTIVE' AND l.is_public=1 AND vrl.ontology_id < 1000000000 
 AND (SELECT list_id FROM list_tag WHERE list_id=l.list_id AND tag_id=43) IS NULL 
 AND (SELECT list_id FROM list_tag WHERE list_id=l.list_id AND tag_id=55) IS NULL 
 AND (SELECT list_id FROM list_tag WHERE list_id=l.list_id AND tag_id=246403) IS NOT NULL 
ORDER BY vrl.moved_date DESC LIMIT 200;

EXPLAIN zeigt:

+----+--------------------+----------+--------+-----------------------------------------------------+--------------+---------+-------------------------------------------------+------+--------------------------+
| id | select_type        | table    | type   | possible_keys                                       | key          | key_len | ref                                             | rows | Extra                    |
+----+--------------------+----------+--------+-----------------------------------------------------+--------------+---------+-------------------------------------------------+------+--------------------------+
|  1 | PRIMARY            | vrl      | index  | PRIMARY                                             | moved_date   | 8       | NULL                                            |  200 | Using where              |
|  1 | PRIMARY            | l        | eq_ref | PRIMARY,status,ispublic,idx_lookup,is_public_status | PRIMARY      | 4       | ranker.vrl.list_id                              |    1 | Using where              |
|  1 | PRIMARY            | vrlih    | eq_ref | PRIMARY                                             | PRIMARY      | 9       | ranker.vrl.list_id,ranker.vrl.ontology_id,const |    1 | Using where              |
|  1 | PRIMARY            | lbs      | eq_ref | PRIMARY,idx_list_burial_state,burial_score          | PRIMARY      | 4       | ranker.vrl.list_id                              |    1 | Using where              |
|  4 | DEPENDENT SUBQUERY | list_tag | ref    | list_tag_key,list_id,tag_id                         | list_tag_key | 9       | ranker.l.list_id,const                          |    1 | Using where; Using index |
|  3 | DEPENDENT SUBQUERY | list_tag | ref    | list_tag_key,list_id,tag_id                         | list_tag_key | 9       | ranker.l.list_id,const                          |    1 | Using where; Using index |
|  2 | DEPENDENT SUBQUERY | list_tag | ref    | list_tag_key,list_id,tag_id                         | list_tag_key | 9       | ranker.l.list_id,const                          |    1 | Using where; Using index |
+----+--------------------+----------+--------+-----------------------------------------------------+--------------+---------+-------------------------------------------------+------+--------------------------+

Die gleiche Abfrage mit JOINs lautet:

EXPLAIN SELECT vrl.list_id,vrl.ontology_id,vrl.position,l.name AS list_name, vrlih.position AS previous_position, vrl.moved_date 
FROM `vote-ranked-listory` vrl 
INNER JOIN lists l ON l.list_id = vrl.list_id 
INNER JOIN `vote-ranked-list-item-history` vrlih ON vrl.list_id = vrlih.list_id AND vrl.ontology_id=vrlih.ontology_id AND vrlih.type='PREVIOUS_POSITION' 
INNER JOIN list_burial_state lbs ON lbs.list_id = vrl.list_id AND lbs.burial_score < 0.5 
LEFT JOIN list_tag lt1 ON lt1.list_id = vrl.list_id AND lt1.tag_id = 43 
LEFT JOIN list_tag lt2 ON lt2.list_id = vrl.list_id AND lt2.tag_id = 55 
INNER JOIN list_tag lt3 ON lt3.list_id = vrl.list_id AND lt3.tag_id = 246403 
WHERE vrl.position <= 15 AND l.status='ACTIVE' AND l.is_public=1 AND vrl.ontology_id < 1000000000 
AND lt1.list_id IS NULL AND lt2.tag_id IS NULL 
ORDER BY vrl.moved_date DESC LIMIT 200;

und die Ausgabe ist:

+----+-------------+-------+--------+-----------------------------------------------------+--------------+---------+---------------------------------------------+------+----------------------------------------------+
| id | select_type | table | type   | possible_keys                                       | key          | key_len | ref                                         | rows | Extra                                        |
+----+-------------+-------+--------+-----------------------------------------------------+--------------+---------+---------------------------------------------+------+----------------------------------------------+
|  1 | SIMPLE      | lt3   | ref    | list_tag_key,list_id,tag_id                         | tag_id       | 5       | const                                       | 2386 | Using where; Using temporary; Using filesort |
|  1 | SIMPLE      | l     | eq_ref | PRIMARY,status,ispublic,idx_lookup,is_public_status | PRIMARY      | 4       | ranker.lt3.list_id                          |    1 | Using where                                  |
|  1 | SIMPLE      | vrlih | ref    | PRIMARY                                             | PRIMARY      | 4       | ranker.lt3.list_id                          |  103 | Using where                                  |
|  1 | SIMPLE      | vrl   | ref    | PRIMARY                                             | PRIMARY      | 8       | ranker.lt3.list_id,ranker.vrlih.ontology_id |   65 | Using where                                  |
|  1 | SIMPLE      | lt1   | ref    | list_tag_key,list_id,tag_id                         | list_tag_key | 9       | ranker.lt3.list_id,const                    |    1 | Using where; Using index; Not exists         |
|  1 | SIMPLE      | lbs   | eq_ref | PRIMARY,idx_list_burial_state,burial_score          | PRIMARY      | 4       | ranker.vrl.list_id                          |    1 | Using where                                  |
|  1 | SIMPLE      | lt2   | ref    | list_tag_key,list_id,tag_id                         | list_tag_key | 9       | ranker.lt3.list_id,const                    |    1 | Using where; Using index                     |
+----+-------------+-------+--------+-----------------------------------------------------+--------------+---------+---------------------------------------------+------+----------------------------------------------+

Ein Vergleich der rowsSpalte zeigt den Unterschied und die Abfrage mit JOINs wird verwendet Using temporary; Using filesort.

Wenn ich beide Abfragen ausführe, ist die erste in 0,02 Sekunden erledigt, die zweite wird auch nach 1 Minute nicht abgeschlossen, daher hat EXPLAIN diese Abfragen richtig erklärt.

Wenn ich den INNER JOIN nicht auf dem list_tagTisch habe, dh wenn ich entferne

AND (SELECT list_id FROM list_tag WHERE list_id=l.list_id AND tag_id=246403) IS NOT NULL  

ab der ersten Abfrage und entsprechend:

INNER JOIN list_tag lt3 ON lt3.list_id = vrl.list_id AND lt3.tag_id = 246403

Ab der zweiten Abfrage gibt EXPLAIN für beide Abfragen die gleiche Anzahl von Zeilen zurück, und beide Abfragen werden gleich schnell ausgeführt.


Ich habe eine ähnliche Situation, aber mit mehr Joins als Ihrer, werde ich versuchen, einmal zu erklären
Pahnin

In Oracle oder PostgreSQL hätte ich versucht: UND NICHT EXISTIERT (SELECT 1 FROM list_tag WHERE list_id = l.list_id AND tag_id in (43, 55, 246403))
David Aldridge

11

Unterabfragen können Aggregationsfunktionen im laufenden Betrieb berechnen. ZB Finden Sie den Mindestpreis des Buches und erhalten Sie alle Bücher, die mit diesem Preis verkauft werden. 1) Verwenden von Unterabfragen:

SELECT titles, price
FROM Books, Orders
WHERE price = 
(SELECT MIN(price)
 FROM Orders) AND (Books.ID=Orders.ID);

2) Verwenden von JOINs

SELECT MIN(price)
     FROM Orders;
-----------------
2.99

SELECT titles, price
FROM Books b
INNER JOIN  Orders o
ON b.ID = o.ID
WHERE o.price = 2.99;

Ein anderer Fall: mehrere GROUP BYs mit unterschiedlichen Tabellen: stackoverflow.com/questions/11415284/… Unterabfragen scheinen streng allgemeiner zu sein. Siehe auch den MySQL-Mann: dev.mysql.com/doc/refman/5.7/en/optimizing-subqueries.html | dev.mysql.com/doc/refman/5.7/de/rewriting-subqueries.html
Ciro Santilli 12 病毒 审查 六四 事件 12

6
-1 Dies ist irreführend, da Sie eine Unterabfrage verwenden und an beiden Beispielen teilnehmen. Dass Sie die Unterabfrage in eine zweite Abfrage gezogen haben, um den niedrigsten Bestellpreis zu ermitteln, hat keine Auswirkung, da die Datenbank genau dasselbe tut. Außerdem schreiben Sie den Join nicht mithilfe einer Unterabfrage neu. Beide Abfragen verwenden einen Join. Sie haben Recht, dass Unterabfragen Aggregatfunktionen zulassen, aber dieses Beispiel zeigt diese Tatsache nicht.
David Harkness

Ich stimme David zu, und Sie können group by verwenden, um den Mindestpreis zu erhalten.
user1735921

9
  • Eine allgemeine Regel ist, dass Verknüpfungen in den meisten Fällen schneller sind (99%).
  • Je mehr Datentabellen vorhanden sind, desto langsamer sind die Unterabfragen .
  • Je weniger Datentabellen haben, die Unterabfragen entsprechende Geschwindigkeit haben wie verbindet .
  • Die Unterabfragen sind einfacher, verständlicher und leichter zu lesen.
  • Die meisten Web- und App-Frameworks sowie deren "ORMs" und "Aktive Datensätze " generieren Abfragen mit Unterabfragen , da mit Unterabfragen die Aufteilung der Verantwortung, das Verwalten von Code usw. einfacher ist.
  • Für kleinere Websites oder Apps sind Unterabfragen in Ordnung. Bei größeren Websites und Apps müssen Sie jedoch generierte Abfragen häufig neu schreiben, um Abfragen zu verbinden , insbesondere wenn eine Abfrage viele Unterabfragen in der Abfrage verwendet.

Einige Leute sagen, "einige RDBMS können eine Unterabfrage in einen Join oder einen Join in eine Unterabfrage umschreiben, wenn sie glauben , dass eine schneller als die andere ist.", Aber diese Aussage gilt für einfache Fälle, sicherlich nicht für komplizierte Abfragen mit Unterabfragen, die tatsächlich a verursachen Leistungsprobleme.


> aber diese Aussage gilt für einfache Fälle. Ich verstehe, dass es sich entweder um einen einfachen Fall handelt, der von RDBMS in "JOIN" umgeschrieben werden kann, oder um einen so komplexen Fall, dass Unterabfragen hier angemessen sind. :-) Schöner Punkt zu ORMs. Ich denke, das hat den größten Einfluss.
Pilat

4

Der Unterschied wird nur sichtbar, wenn die zweite Verbindungstabelle wesentlich mehr Daten enthält als die Primärtabelle. Ich hatte eine Erfahrung wie unten ...

Wir hatten eine Benutzertabelle mit einhunderttausend Einträgen und deren Mitgliedschaftsdaten (Freundschaft) von ungefähr dreihunderttausend Einträgen. Es war eine Join-Anweisung, um Freunde und ihre Daten aufzunehmen, aber mit großer Verzögerung. Aber es funktionierte gut, wenn die Mitgliedschaftstabelle nur eine geringe Datenmenge enthielt. Nachdem wir es geändert hatten, um eine Unterabfrage zu verwenden, funktionierte es einwandfrei.

In der Zwischenzeit arbeiten die Join-Abfragen jedoch mit anderen Tabellen, die weniger Einträge als die Primärtabelle haben.

Daher denke ich, dass die Join- und Sub-Query-Anweisungen einwandfrei funktionieren und von den Daten und der Situation abhängen.


3

Heutzutage können viele Datenbankanbieter Unterabfragen und Verknüpfungen optimieren. Sie müssen Ihre Anfrage also einfach mit EXPLAIN untersuchen und feststellen, welche schneller ist. Wenn es keinen großen Unterschied in der Leistung gibt, bevorzuge ich die Verwendung von Unterabfragen, da diese einfach und leichter zu verstehen sind.


1

Ich denke nur an das gleiche Problem, aber ich verwende die Unterabfrage im FROM-Teil. Ich muss eine Verbindung herstellen und von großen Tabellen abfragen, die "Slave" -Tabelle hat 28 Millionen Datensätze, aber das Ergebnis sind nur 128, also kleine Ergebnisse, große Datenmengen! Ich benutze die MAX () Funktion darauf.

Erstens verwende ich LEFT JOIN, weil ich denke, dass dies der richtige Weg ist, das MySQL kann sich optimieren usw. Zum zweiten Mal schreibe ich nur zum Testen um, um eine Unterauswahl gegen das JOIN zu treffen.

LEFT JOIN-Laufzeit: 1,12 s SUB-SELECT-Laufzeit: 0,06 s

18 mal schneller die Unterauswahl als der Join! Nur im Chokito Adv. Die Unterauswahl sieht schrecklich aus, aber das Ergebnis ...


-1

Wenn Sie Ihre Abfrage mit join beschleunigen möchten:

Verwenden Sie für "inner join / join" nicht die where-Bedingung, sondern die Bedingung "ON". Z.B:

     select id,name from table1 a  
   join table2 b on a.name=b.name
   where id='123'

 Try,

    select id,name from table1 a  
   join table2 b on a.name=b.name and a.id='123'

Verwenden Sie für "Links / Rechts-Verknüpfung" nicht die Option "EIN", da bei Verwendung der Links / Rechts-Verknüpfung alle Zeilen für eine Tabelle abgerufen werden. Daher wird die Verwendung in "Ein" nicht verwendet. Versuchen Sie also, die Bedingung "Wo" zu verwenden


Dies hängt vom SQL Server und von der Komplexität der Abfrage ab. Viele SQL-Implementierungen würden einfache Abfragen wie diese für die beste Leistung optimieren. Geben Sie möglicherweise einen Beispielservernamen und eine Beispielversion an, bei denen dieses Verhalten die Antwort verbessert.
Trendfischer
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.