SQL-Abfragen geben Daten aus mehreren Tabellen zurück


434

Ich würde gerne folgendes wissen:

  • Wie bekomme ich Daten aus mehreren Tabellen in meiner Datenbank?
  • Welche Methoden gibt es dafür?
  • Was sind Joins und Gewerkschaften und wie unterscheiden sie sich voneinander?
  • Wann sollte ich jeden im Vergleich zu den anderen verwenden?

Ich habe vor, dies in meiner (z. B. PHP) Anwendung zu verwenden, möchte aber nicht mehrere Abfragen für die Datenbank ausführen. Welche Optionen habe ich, um Daten aus mehreren Tabellen in einer einzigen Abfrage abzurufen?

Hinweis: Ich schreibe dies, da ich in der Lage sein möchte, auf einen gut geschriebenen Leitfaden zu den zahlreichen Fragen zu verweisen, die mir ständig in der PHP-Warteschlange begegnen, damit ich beim Posten einer Antwort weitere Details darauf verlinken kann.

Die Antworten decken Folgendes ab:

  1. Teil 1 - Beitritte und Gewerkschaften
  2. Teil 2 - Unterabfragen
  3. Teil 3 - Tricks und effizienter Code
  4. Teil 4 - Unterabfragen in der From-Klausel
  5. Teil 5 - Gemischte Tüte mit Johns Tricks

Antworten:


469

Teil 1 - Beitritte und Gewerkschaften

Diese Antwort umfasst:

  1. Teil 1
  2. Teil 2
    • Unterabfragen - was sie sind, wo sie verwendet werden können und worauf zu achten ist
    • Cartesian tritt der AKA bei - Oh, das Elend!

Es gibt verschiedene Möglichkeiten, Daten aus mehreren Tabellen in einer Datenbank abzurufen. In dieser Antwort verwende ich die ANSI-92-Join-Syntax. Dies unterscheidet sich möglicherweise von einer Reihe anderer Tutorials, die die ältere ANSI-89-Syntax verwenden (und wenn Sie an 89 gewöhnt sind, scheint dies viel weniger intuitiv zu sein - aber ich kann nur sagen, es zu versuchen), da es viel einfacher ist zu verstehen, wann die Abfragen komplexer werden. Warum es benutzen? Gibt es einen Leistungsgewinn? Die kurze Antwort lautet nein, aber es ist einfacher zu lesen, wenn Sie sich daran gewöhnt haben. Mit dieser Syntax ist es einfacher, Abfragen zu lesen, die von anderen Personen geschrieben wurden.

Ich werde auch das Konzept eines kleinen Karyards verwenden, der über eine Datenbank verfügt, um zu verfolgen, welche Autos verfügbar sind. Der Eigentümer hat Sie als seinen IT-Computer-Mitarbeiter eingestellt und erwartet, dass Sie ihm die Daten, nach denen er fragt, im Handumdrehen löschen können.

Ich habe eine Reihe von Nachschlagetabellen erstellt, die vom Final Table verwendet werden. Dies gibt uns ein vernünftiges Modell, nach dem wir arbeiten können. Zu Beginn führe ich meine Abfragen für eine Beispieldatenbank mit der folgenden Struktur aus. Ich werde versuchen, an häufige Fehler zu denken, die beim Start gemacht werden, und erklären, was mit ihnen schief geht - und natürlich zeigen, wie man sie korrigiert.

Die erste Tabelle ist einfach eine Farbliste, damit wir wissen, welche Farben wir im Autohof haben.

mysql> create table colors(id int(3) not null auto_increment primary key, 
    -> color varchar(15), paint varchar(10));
Query OK, 0 rows affected (0.01 sec)

mysql> show columns from colors;
+-------+-------------+------+-----+---------+----------------+
| Field | Type        | Null | Key | Default | Extra          |
+-------+-------------+------+-----+---------+----------------+
| id    | int(3)      | NO   | PRI | NULL    | auto_increment |
| color | varchar(15) | YES  |     | NULL    |                |
| paint | varchar(10) | YES  |     | NULL    |                |
+-------+-------------+------+-----+---------+----------------+
3 rows in set (0.01 sec)

mysql> insert into colors (color, paint) values ('Red', 'Metallic'), 
    -> ('Green', 'Gloss'), ('Blue', 'Metallic'), 
    -> ('White' 'Gloss'), ('Black' 'Gloss');
Query OK, 5 rows affected (0.00 sec)
Records: 5  Duplicates: 0  Warnings: 0

mysql> select * from colors;
+----+-------+----------+
| id | color | paint    |
+----+-------+----------+
|  1 | Red   | Metallic |
|  2 | Green | Gloss    |
|  3 | Blue  | Metallic |
|  4 | White | Gloss    |
|  5 | Black | Gloss    |
+----+-------+----------+
5 rows in set (0.00 sec)

Die Markentabelle identifiziert die verschiedenen Marken der Autos, die Caryard möglicherweise verkaufen könnte.

mysql> create table brands (id int(3) not null auto_increment primary key, 
    -> brand varchar(15));
Query OK, 0 rows affected (0.01 sec)

mysql> show columns from brands;
+-------+-------------+------+-----+---------+----------------+
| Field | Type        | Null | Key | Default | Extra          |
+-------+-------------+------+-----+---------+----------------+
| id    | int(3)      | NO   | PRI | NULL    | auto_increment |
| brand | varchar(15) | YES  |     | NULL    |                |
+-------+-------------+------+-----+---------+----------------+
2 rows in set (0.01 sec)

mysql> insert into brands (brand) values ('Ford'), ('Toyota'), 
    -> ('Nissan'), ('Smart'), ('BMW');
Query OK, 5 rows affected (0.00 sec)
Records: 5  Duplicates: 0  Warnings: 0

mysql> select * from brands;
+----+--------+
| id | brand  |
+----+--------+
|  1 | Ford   |
|  2 | Toyota |
|  3 | Nissan |
|  4 | Smart  |
|  5 | BMW    |
+----+--------+
5 rows in set (0.00 sec)

Die Modelltabelle deckt verschiedene Fahrzeugtypen ab. Es wird einfacher sein, unterschiedliche Fahrzeugtypen als tatsächliche Fahrzeugmodelle zu verwenden.

mysql> create table models (id int(3) not null auto_increment primary key, 
    -> model varchar(15));
Query OK, 0 rows affected (0.01 sec)

mysql> show columns from models;
+-------+-------------+------+-----+---------+----------------+
| Field | Type        | Null | Key | Default | Extra          |
+-------+-------------+------+-----+---------+----------------+
| id    | int(3)      | NO   | PRI | NULL    | auto_increment |
| model | varchar(15) | YES  |     | NULL    |                |
+-------+-------------+------+-----+---------+----------------+
2 rows in set (0.00 sec)

mysql> insert into models (model) values ('Sports'), ('Sedan'), ('4WD'), ('Luxury');
Query OK, 4 rows affected (0.00 sec)
Records: 4  Duplicates: 0  Warnings: 0

mysql> select * from models;
+----+--------+
| id | model  |
+----+--------+
|  1 | Sports |
|  2 | Sedan  |
|  3 | 4WD    |
|  4 | Luxury |
+----+--------+
4 rows in set (0.00 sec)

Und schließlich, um all diese anderen Tische zusammenzubinden, den Tisch, der alles zusammenhält. Das ID-Feld ist tatsächlich die eindeutige Chargennummer, mit der Autos identifiziert werden.

mysql> create table cars (id int(3) not null auto_increment primary key, 
    -> color int(3), brand int(3), model int(3));
Query OK, 0 rows affected (0.01 sec)

mysql> show columns from cars;
+-------+--------+------+-----+---------+----------------+
| Field | Type   | Null | Key | Default | Extra          |
+-------+--------+------+-----+---------+----------------+
| id    | int(3) | NO   | PRI | NULL    | auto_increment |
| color | int(3) | YES  |     | NULL    |                |
| brand | int(3) | YES  |     | NULL    |                |
| model | int(3) | YES  |     | NULL    |                |
+-------+--------+------+-----+---------+----------------+
4 rows in set (0.00 sec)

mysql> insert into cars (color, brand, model) values (1,2,1), (3,1,2), (5,3,1), 
    -> (4,4,2), (2,2,3), (3,5,4), (4,1,3), (2,2,1), (5,2,3), (4,5,1);
Query OK, 10 rows affected (0.00 sec)
Records: 10  Duplicates: 0  Warnings: 0

mysql> select * from cars;
+----+-------+-------+-------+
| id | color | brand | model |
+----+-------+-------+-------+
|  1 |     1 |     2 |     1 |
|  2 |     3 |     1 |     2 |
|  3 |     5 |     3 |     1 |
|  4 |     4 |     4 |     2 |
|  5 |     2 |     2 |     3 |
|  6 |     3 |     5 |     4 |
|  7 |     4 |     1 |     3 |
|  8 |     2 |     2 |     1 |
|  9 |     5 |     2 |     3 |
| 10 |     4 |     5 |     1 |
+----+-------+-------+-------+
10 rows in set (0.00 sec)

Dies gibt uns genügend Daten (ich hoffe), um die folgenden Beispiele für verschiedene Arten von Verknüpfungen abzudecken, und gibt auch genügend Daten, damit sie sich lohnen.

Der Chef möchte also die IDs aller Sportwagen kennen, die er hat .

Dies ist ein einfacher Join mit zwei Tabellen. Wir haben eine Tabelle, die das Modell und die Tabelle mit dem verfügbaren Bestand identifiziert. Wie Sie sehen können, beziehen sich die Daten in der modelSpalte der carsTabelle auf die modelsSpalte der carsTabelle, die wir haben. Jetzt wissen wir, dass die Modelltabelle eine ID von 1für hat, Sportsalso schreiben wir den Join.

select
    ID,
    model
from
    cars
        join models
            on model=ID

Diese Abfrage sieht also gut aus, oder? Wir haben die beiden Tabellen identifiziert und enthalten die benötigten Informationen. Wir verwenden einen Join, der korrekt angibt, an welchen Spalten verbunden werden soll.

ERROR 1052 (23000): Column 'ID' in field list is ambiguous

Oh nein! Ein Fehler in unserer ersten Abfrage! Ja, und es ist eine Pflaume. Sie sehen, die Abfrage hat zwar die richtigen Spalten, aber einige davon sind in beiden Tabellen vorhanden, sodass die Datenbank verwirrt darüber ist, welche Spalte wir tatsächlich meinen und wo. Es gibt zwei Lösungen, um dies zu lösen. Das erste ist nett und einfach, wir können verwenden tableName.columnName, um der Datenbank genau zu sagen, was wir meinen, so:

select
    cars.ID,
    models.model
from
    cars
        join models
            on cars.model=models.ID

+----+--------+
| ID | model  |
+----+--------+
|  1 | Sports |
|  3 | Sports |
|  8 | Sports |
| 10 | Sports |
|  2 | Sedan  |
|  4 | Sedan  |
|  5 | 4WD    |
|  7 | 4WD    |
|  9 | 4WD    |
|  6 | Luxury |
+----+--------+
10 rows in set (0.00 sec)

Das andere wird wahrscheinlich häufiger verwendet und als Tabellenaliasing bezeichnet. Die Tabellen in diesem Beispiel haben nette und kurze einfache Namen, aber das Eingeben von so etwas KPI_DAILY_SALES_BY_DEPARTMENTwürde wahrscheinlich schnell alt werden. Eine einfache Möglichkeit besteht darin, die Tabelle wie folgt zu benennen:

select
    a.ID,
    b.model
from
    cars a
        join models b
            on a.model=b.ID

Nun zurück zur Anfrage. Wie Sie sehen, haben wir die Informationen, die wir benötigen, aber wir haben auch Informationen, die nicht angefordert wurden. Daher müssen wir eine where-Klausel in die Erklärung aufnehmen, um nur die Sportwagen zu erhalten, wie sie angefordert wurden. Da ich die Tabellenalias-Methode bevorzuge, anstatt die Tabellennamen immer wieder zu verwenden, werde ich mich ab diesem Punkt daran halten.

Natürlich müssen wir unserer Abfrage eine where-Klausel hinzufügen. Wir können Sportwagen entweder durch ID=1oder identifizieren model='Sports'. Da die ID indiziert ist und der Primärschlüssel (und zufällig weniger eingegeben wird), können Sie dies in unserer Abfrage verwenden.

select
    a.ID,
    b.model
from
    cars a
        join models b
            on a.model=b.ID
where
    b.ID=1

+----+--------+
| ID | model  |
+----+--------+
|  1 | Sports |
|  3 | Sports |
|  8 | Sports |
| 10 | Sports |
+----+--------+
4 rows in set (0.00 sec)

Bingo! Der Chef ist glücklich. Da er ein Chef ist und nie glücklich mit dem ist, wonach er gefragt hat, schaut er sich die Informationen an und sagt dann, ich möchte auch die Farben .

Okay, wir haben einen Großteil unserer Abfrage bereits geschrieben, aber wir müssen eine dritte Tabelle verwenden, die Farben enthält. In unserer Hauptinformationstabelle wird nun carsdie Fahrzeugfarb-ID gespeichert und diese führt zurück zur Spalte Farb-ID. Ähnlich wie beim Original können wir uns also einer dritten Tabelle anschließen:

select
    a.ID,
    b.model
from
    cars a
        join models b
            on a.model=b.ID
        join colors c
            on a.color=c.ID
where
    b.ID=1

+----+--------+
| ID | model  |
+----+--------+
|  1 | Sports |
|  3 | Sports |
|  8 | Sports |
| 10 | Sports |
+----+--------+
4 rows in set (0.00 sec)

Verdammt, obwohl die Tabelle korrekt verknüpft und die zugehörigen Spalten verknüpft waren, haben wir vergessen, die tatsächlichen Informationen aus der neuen Tabelle abzurufen, die wir gerade verknüpft haben.

select
    a.ID,
    b.model,
    c.color
from
    cars a
        join models b
            on a.model=b.ID
        join colors c
            on a.color=c.ID
where
    b.ID=1

+----+--------+-------+
| ID | model  | color |
+----+--------+-------+
|  1 | Sports | Red   |
|  8 | Sports | Green |
| 10 | Sports | White |
|  3 | Sports | Black |
+----+--------+-------+
4 rows in set (0.00 sec)

Richtig, das ist für einen Moment der Boss von unserem Rücken. Um dies etwas näher zu erläutern. Wie Sie sehen können, fromverknüpft die Klausel in unserer Anweisung unsere Haupttabelle (ich verwende häufig eine Tabelle, die Informationen anstelle einer Nachschlagetabelle oder Dimensionstabelle enthält. Die Abfrage funktioniert genauso gut, wenn alle Tabellen vertauscht sind, ist aber weniger sinnvoll, wenn Wir kommen auf diese Abfrage zurück, um sie in ein paar Monaten zu lesen. Daher ist es oft am besten, eine Abfrage zu schreiben, die schön und leicht zu verstehen ist. Legen Sie sie intuitiv an und verwenden Sie einen schönen Einzug, damit alles so klar wie möglich ist Wenn Sie andere unterrichten, versuchen Sie, diese Eigenschaften in ihre Abfragen einzubinden - insbesondere, wenn Sie Fehler beheben.

Es ist durchaus möglich, immer mehr Tabellen auf diese Weise zu verknüpfen.

select
    a.ID,
    b.model,
    c.color
from
    cars a
        join models b
            on a.model=b.ID
        join colors c
            on a.color=c.ID
        join brands d
            on a.brand=d.ID
where
    b.ID=1

Während ich vergessen habe, eine Tabelle aufzunehmen, in der wir möglicherweise mehr als eine Spalte in die joinAnweisung einfügen möchten , ist hier ein Beispiel. Wenn die modelsTabelle markenspezifische Modelle und daher auch eine Spalte mit dem Namen hat, branddie mit der brandsTabelle auf dem IDFeld verknüpft ist , könnte dies folgendermaßen geschehen:

select
    a.ID,
    b.model,
    c.color
from
    cars a
        join models b
            on a.model=b.ID
        join colors c
            on a.color=c.ID
        join brands d
            on a.brand=d.ID
            and b.brand=d.ID
where
    b.ID=1

Sie können sehen, dass die obige Abfrage nicht nur die verknüpften Tabellen mit der carsHaupttabelle verknüpft , sondern auch Verknüpfungen zwischen den bereits verknüpften Tabellen angibt. Wenn dies nicht getan wurde, wird das Ergebnis als kartesische Verknüpfung bezeichnet - was dba für schlecht spricht. Bei einem kartesischen Join werden Zeilen zurückgegeben, da die Informationen der Datenbank nicht mitteilen, wie die Ergebnisse eingeschränkt werden sollen. Daher gibt die Abfrage alle Zeilen zurück, die den Kriterien entsprechen.

Um ein Beispiel für eine kartesische Verknüpfung zu geben, führen Sie die folgende Abfrage aus:

select
    a.ID,
    b.model
from
    cars a
        join models b

+----+--------+
| ID | model  |
+----+--------+
|  1 | Sports |
|  1 | Sedan  |
|  1 | 4WD    |
|  1 | Luxury |
|  2 | Sports |
|  2 | Sedan  |
|  2 | 4WD    |
|  2 | Luxury |
|  3 | Sports |
|  3 | Sedan  |
|  3 | 4WD    |
|  3 | Luxury |
|  4 | Sports |
|  4 | Sedan  |
|  4 | 4WD    |
|  4 | Luxury |
|  5 | Sports |
|  5 | Sedan  |
|  5 | 4WD    |
|  5 | Luxury |
|  6 | Sports |
|  6 | Sedan  |
|  6 | 4WD    |
|  6 | Luxury |
|  7 | Sports |
|  7 | Sedan  |
|  7 | 4WD    |
|  7 | Luxury |
|  8 | Sports |
|  8 | Sedan  |
|  8 | 4WD    |
|  8 | Luxury |
|  9 | Sports |
|  9 | Sedan  |
|  9 | 4WD    |
|  9 | Luxury |
| 10 | Sports |
| 10 | Sedan  |
| 10 | 4WD    |
| 10 | Luxury |
+----+--------+
40 rows in set (0.00 sec)

Guter Gott, das ist hässlich. In Bezug auf die Datenbank ist es jedoch genau das , wonach gefragt wurde. In der Abfrage haben wir nach dem IDvon carsund dem modelvon gefragt models. Da wir jedoch nicht angegeben haben, wie die Tabellen verknüpft werden sollen, hat die Datenbank jede Zeile aus der ersten Tabelle mit jeder Zeile aus der zweiten Tabelle abgeglichen .

Okay, der Chef ist zurück und möchte wieder mehr Informationen. Ich möchte die gleiche Liste, aber auch 4WDs darin enthalten .

Dies gibt uns jedoch eine gute Ausrede, zwei verschiedene Wege zu betrachten, um dies zu erreichen. Wir könnten der where-Klausel eine weitere Bedingung hinzufügen:

select
    a.ID,
    b.model,
    c.color
from
    cars a
        join models b
            on a.model=b.ID
        join colors c
            on a.color=c.ID
        join brands d
            on a.brand=d.ID
where
    b.ID=1
    or b.ID=3

Obwohl das oben Genannte perfekt funktioniert, sehen wir es uns anders an. Dies ist eine gute Ausrede, um zu zeigen, wie eine unionAbfrage funktioniert.

Wir wissen, dass im Folgenden alle Sportwagen zurückgegeben werden:

select
    a.ID,
    b.model,
    c.color
from
    cars a
        join models b
            on a.model=b.ID
        join colors c
            on a.color=c.ID
        join brands d
            on a.brand=d.ID
where
    b.ID=1

Und das Folgende würde alle 4WDs zurückgeben:

select
    a.ID,
    b.model,
    c.color
from
    cars a
        join models b
            on a.model=b.ID
        join colors c
            on a.color=c.ID
        join brands d
            on a.brand=d.ID
where
    b.ID=3

Durch Hinzufügen einer union allKlausel zwischen ihnen werden die Ergebnisse der zweiten Abfrage an die Ergebnisse der ersten Abfrage angehängt.

select
    a.ID,
    b.model,
    c.color
from
    cars a
        join models b
            on a.model=b.ID
        join colors c
            on a.color=c.ID
        join brands d
            on a.brand=d.ID
where
    b.ID=1
union all
select
    a.ID,
    b.model,
    c.color
from
    cars a
        join models b
            on a.model=b.ID
        join colors c
            on a.color=c.ID
        join brands d
            on a.brand=d.ID
where
    b.ID=3

+----+--------+-------+
| ID | model  | color |
+----+--------+-------+
|  1 | Sports | Red   |
|  8 | Sports | Green |
| 10 | Sports | White |
|  3 | Sports | Black |
|  5 | 4WD    | Green |
|  7 | 4WD    | White |
|  9 | 4WD    | Black |
+----+--------+-------+
7 rows in set (0.00 sec)

Wie Sie sehen, werden die Ergebnisse der ersten Abfrage zuerst zurückgegeben, gefolgt von den Ergebnissen der zweiten Abfrage.

In diesem Beispiel wäre es natürlich viel einfacher gewesen, einfach die erste Abfrage zu verwenden, aber unionAbfragen können für bestimmte Fälle großartig sein. Sie sind eine großartige Möglichkeit, bestimmte Ergebnisse aus Tabellen aus Tabellen zurückzugeben, die nicht einfach miteinander verbunden werden können - oder aus völlig unabhängigen Tabellen. Es gibt jedoch einige Regeln zu befolgen.

  • Die Spaltentypen aus der ersten Abfrage müssen mit den Spaltentypen aus jeder anderen Abfrage unten übereinstimmen.
  • Die Namen der Spalten aus der ersten Abfrage werden verwendet, um die gesamte Ergebnismenge zu identifizieren.
  • Die Anzahl der Spalten in jeder Abfrage muss gleich sein.

Jetzt fragen Sie sich vielleicht , was der Unterschied zwischen der Verwendung von unionund ist union all. Eine unionAbfrage entfernt Duplikate, eine union allnicht. Dies bedeutet, dass es bei der Verwendung von unionover einen kleinen Leistungseinbruch gibt, union allaber die Ergebnisse können sich lohnen - ich werde jedoch nicht darüber spekulieren.

In diesem Zusammenhang sollten einige zusätzliche Hinweise erwähnt werden.

  • Wenn wir die Ergebnisse bestellen wollten, können wir einen verwenden, order byaber Sie können den Alias ​​nicht mehr verwenden. In der obigen Abfrage order by a.IDwürde das Anhängen eines zu einem Fehler führen - was die Ergebnisse betrifft, wird die Spalte IDeher aufgerufen als a.ID- obwohl in beiden Abfragen derselbe Alias ​​verwendet wurde.
  • Wir können nur eine order byAussage haben, und es muss die letzte sein.

Für die nächsten Beispiele füge ich unseren Tabellen einige zusätzliche Zeilen hinzu.

Ich habe Holdender Markentabelle hinzugefügt . Ich habe auch eine Zeile hinzugefügt cars, die den colorWert 12- hat und die keinen Bezug in der Farbtabelle hat.

Okay, der Chef ist wieder da und bellt Anfragen an - * Ich möchte eine Zählung jeder Marke, die wir führen, und der Anzahl der Autos darin! `- Typischerweise kommen wir nur zu einem interessanten Abschnitt unserer Diskussion und der Chef möchte mehr Arbeit .

Richtig, das erste, was wir tun müssen, ist eine vollständige Liste möglicher Marken zu erhalten.

select
    a.brand
from
    brands a

+--------+
| brand  |
+--------+
| Ford   |
| Toyota |
| Nissan |
| Smart  |
| BMW    |
| Holden |
+--------+
6 rows in set (0.00 sec)

Wenn wir dies nun mit unserem Autotisch verbinden, erhalten wir das folgende Ergebnis:

select
    a.brand
from
    brands a
        join cars b
            on a.ID=b.brand
group by
    a.brand

+--------+
| brand  |
+--------+
| BMW    |
| Ford   |
| Nissan |
| Smart  |
| Toyota |
+--------+
5 rows in set (0.00 sec)

Was natürlich ein Problem ist - wir sehen keine Erwähnung der schönen HoldenMarke, die ich hinzugefügt habe.

Dies liegt daran, dass ein Join in beiden Tabellen nach übereinstimmenden Zeilen sucht . Da in Fahrzeugen vom Typ keine Daten vorhanden sind, werden Holdendiese nicht zurückgegeben. Hier können wir einen outerJoin verwenden. Dadurch werden alle Ergebnisse einer Tabelle zurückgegeben, unabhängig davon, ob sie mit der anderen Tabelle übereinstimmen oder nicht:

select
    a.brand
from
    brands a
        left outer join cars b
            on a.ID=b.brand
group by
    a.brand

+--------+
| brand  |
+--------+
| BMW    |
| Ford   |
| Holden |
| Nissan |
| Smart  |
| Toyota |
+--------+
6 rows in set (0.00 sec)

Jetzt, wo wir das haben, können wir eine schöne Aggregatfunktion hinzufügen, um eine Zählung zu erhalten und den Chef für einen Moment von unserem Rücken zu bekommen.

select
    a.brand,
    count(b.id) as countOfBrand
from
    brands a
        left outer join cars b
            on a.ID=b.brand
group by
    a.brand

+--------+--------------+
| brand  | countOfBrand |
+--------+--------------+
| BMW    |            2 |
| Ford   |            2 |
| Holden |            0 |
| Nissan |            1 |
| Smart  |            1 |
| Toyota |            5 |
+--------+--------------+
6 rows in set (0.00 sec)

Und damit schleicht sich der Chef weg.

Um dies näher zu erläutern, können äußere Verknüpfungen vom Typ leftoder sein right. Links oder rechts definiert, welche Tabelle vollständig enthalten ist. A left outer joinenthält alle Zeilen aus der Tabelle links, während (Sie haben es erraten) a right outer joinalle Ergebnisse aus der Tabelle rechts in die Ergebnisse einbringt.

Einige Datenbanken erlauben eine, full outer joindie Ergebnisse (ob übereinstimmend oder nicht) aus beiden Tabellen zurückbringt , dies wird jedoch nicht in allen Datenbanken unterstützt.

Nun, ich nehme an, zu diesem Zeitpunkt fragen Sie sich wahrscheinlich, ob Sie Join-Typen in einer Abfrage zusammenführen können oder nicht - und die Antwort lautet: Ja, das können Sie absolut.

select
    b.brand,
    c.color,
    count(a.id) as countOfBrand
from
    cars a
        right outer join brands b
            on b.ID=a.brand
        join colors c
            on a.color=c.ID
group by
    a.brand,
    c.color

+--------+-------+--------------+
| brand  | color | countOfBrand |
+--------+-------+--------------+
| Ford   | Blue  |            1 |
| Ford   | White |            1 |
| Toyota | Black |            1 |
| Toyota | Green |            2 |
| Toyota | Red   |            1 |
| Nissan | Black |            1 |
| Smart  | White |            1 |
| BMW    | Blue  |            1 |
| BMW    | White |            1 |
+--------+-------+--------------+
9 rows in set (0.00 sec)

Warum sind das nicht die erwarteten Ergebnisse? Dies liegt daran, dass wir zwar die äußere Verknüpfung von Autos zu Marken ausgewählt haben, diese jedoch nicht in der Verknüpfung zu Farben angegeben wurde. Daher führt diese bestimmte Verknüpfung nur zu Ergebnissen, die in beiden Tabellen übereinstimmen.

Hier ist die Abfrage, die funktionieren würde, um die erwarteten Ergebnisse zu erzielen:

select
    a.brand,
    c.color,
    count(b.id) as countOfBrand
from
    brands a
        left outer join cars b
            on a.ID=b.brand
        left outer join colors c
            on b.color=c.ID
group by
    a.brand,
    c.color

+--------+-------+--------------+
| brand  | color | countOfBrand |
+--------+-------+--------------+
| BMW    | Blue  |            1 |
| BMW    | White |            1 |
| Ford   | Blue  |            1 |
| Ford   | White |            1 |
| Holden | NULL  |            0 |
| Nissan | Black |            1 |
| Smart  | White |            1 |
| Toyota | NULL  |            1 |
| Toyota | Black |            1 |
| Toyota | Green |            2 |
| Toyota | Red   |            1 |
+--------+-------+--------------+
11 rows in set (0.00 sec)

Wie wir sehen können, haben wir zwei äußere Verknüpfungen in der Abfrage und die Ergebnisse kommen wie erwartet durch.

Wie wäre es nun mit den anderen Arten von Joins, die Sie fragen? Was ist mit Kreuzungen?

Nun, nicht alle Datenbanken unterstützen das, intersectionaber so ziemlich alle Datenbanken ermöglichen es Ihnen, eine Schnittmenge durch einen Join (oder zumindest eine gut strukturierte where-Anweisung) zu erstellen.

Ein Schnittpunkt ist eine Art von Verknüpfung, die einer unionwie oben beschrieben ähnelt. Der Unterschied besteht jedoch darin, dass nur Datenzeilen zurückgegeben werden, die zwischen den verschiedenen einzelnen Abfragen, die durch die Vereinigung verbunden sind, identisch sind (und ich meine identisch). Es werden nur Zeilen zurückgegeben, die in jeder Hinsicht identisch sind.

Ein einfaches Beispiel wäre als solches:

select
    *
from
    colors
where
    ID>2
intersect
select
    *
from
    colors
where
    id<4

Während eine normale unionAbfrage alle Zeilen der Tabelle zurückgeben würde (die erste Abfrage gibt etwas zurück ID>2und die zweite etwas ID<4), was zu einem vollständigen Satz führen würde, würde eine Schnittabfrage nur die Zeilenübereinstimmung zurückgeben, id=3da sie beide Kriterien erfüllt.

Wenn Ihre Datenbank eine intersectAbfrage nicht unterstützt , kann die oben genannte Abfrage problemlos mit der folgenden Abfrage ausgeführt werden:

select
    a.ID,
    a.color,
    a.paint
from
    colors a
        join colors b
            on a.ID=b.ID
where
    a.ID>2
    and b.ID<4

+----+-------+----------+
| ID | color | paint    |
+----+-------+----------+
|  3 | Blue  | Metallic |
+----+-------+----------+
1 row in set (0.00 sec)

Wenn Sie eine Schnittmenge zwischen zwei verschiedenen Tabellen mithilfe einer Datenbank ausführen möchten, die von Natur aus keine Schnittpunktabfrage unterstützt, müssen Sie für jede Spalte der Tabellen einen Join erstellen .


2
@ Fluffeh Schöne Antworten. Ich habe einen Vorschlag: Wenn Sie es zu einem Killer-SQL-Tutorial machen möchten, müssen Sie nur Venn-Diagramme hinzufügen. Ich habe sofort verstanden, dass sich links und rechts dank ihnen verbinden. Persönliche Anfrage: Haben Sie ein Tutorial zu häufigen Fehlern / Leistungsabstimmungen?
StrayChild01

25
Oh mein. Mein Scrollrad ist kaputt. Tolle Frage und Antwort. Ich wünschte, ich könnte dies 10 Mal positiv bewerten.
Amal Murali

3
Hehe, danke für das positive Feedback. Scrollen Sie weiter, dies war nur die erste Antwort. SO sagte meine Antwort war zu lang, um sie in eine "Antwort" zu passen, also musste ich ein paar verwenden :)
Fluffeh

7
Ehrlich gesagt denke ich, dass diese Antwort etwas gekürzt werden muss.
Einpoklum

Hervorragender Artikel. Datenbank tritt 101.
maqs

101

Ok, ich fand diesen Beitrag sehr interessant und möchte einige meiner Kenntnisse über das Erstellen einer Abfrage teilen. Danke für diesen Fluffeh . Andere, die dies lesen und das Gefühl haben, dass ich falsch liege, können meine Antwort zu 101% bearbeiten und kritisieren. ( Ehrlich gesagt bin ich sehr dankbar, dass ich meine Fehler korrigiert habe. )

Ich werde einige der häufig gestellten Fragen im MySQLTag veröffentlichen.


Trick Nr. 1 ( Zeilen, die mehreren Bedingungen entsprechen )

Angesichts dieses Schemas

CREATE TABLE MovieList
(
    ID INT,
    MovieName VARCHAR(25),
    CONSTRAINT ml_pk PRIMARY KEY (ID),
    CONSTRAINT ml_uq UNIQUE (MovieName)
);

INSERT INTO MovieList VALUES (1, 'American Pie');
INSERT INTO MovieList VALUES (2, 'The Notebook');
INSERT INTO MovieList VALUES (3, 'Discovery Channel: Africa');
INSERT INTO MovieList VALUES (4, 'Mr. Bean');
INSERT INTO MovieList VALUES (5, 'Expendables 2');

CREATE TABLE CategoryList
(
    MovieID INT,
    CategoryName VARCHAR(25),
    CONSTRAINT cl_uq UNIQUE(MovieID, CategoryName),
    CONSTRAINT cl_fk FOREIGN KEY (MovieID) REFERENCES MovieList(ID)
);

INSERT INTO CategoryList VALUES (1, 'Comedy');
INSERT INTO CategoryList VALUES (1, 'Romance');
INSERT INTO CategoryList VALUES (2, 'Romance');
INSERT INTO CategoryList VALUES (2, 'Drama');
INSERT INTO CategoryList VALUES (3, 'Documentary');
INSERT INTO CategoryList VALUES (4, 'Comedy');
INSERT INTO CategoryList VALUES (5, 'Comedy');
INSERT INTO CategoryList VALUES (5, 'Action');

FRAGE

Finden Sie alle Filme , die auf zumindest gehören beide Comedy und RomanceKategorien.

Lösung

Diese Frage kann manchmal sehr schwierig sein. Es scheint, dass eine solche Abfrage die Antwort sein wird: -

SELECT  DISTINCT a.MovieName
FROM    MovieList a
        INNER JOIN CategoryList b
            ON a.ID = b.MovieID
WHERE   b.CategoryName = 'Comedy' AND
        b.CategoryName = 'Romance'

SQLFiddle-Demo

Das ist definitiv sehr falsch, weil es kein Ergebnis liefert . Die Erklärung dafür ist, dass es CategoryNamein jeder Zeile nur einen gültigen Wert von gibt . Beispielsweise gibt die erste Bedingung true zurück , die zweite Bedingung ist immer false. Bei Verwendung des ANDOperators sollten also beide Bedingungen erfüllt sein. Andernfalls ist es falsch. Eine andere Abfrage ist wie folgt:

SELECT  DISTINCT a.MovieName
FROM    MovieList a
        INNER JOIN CategoryList b
            ON a.ID = b.MovieID
WHERE   b.CategoryName IN ('Comedy','Romance')

SQLFiddle-Demo

und das Ergebnis ist immer noch falsch, da es mit einem Datensatz übereinstimmt, der mindestens eine Übereinstimmung enthält categoryName. Die wirkliche Lösung wäre, die Anzahl der Aufzeichnungsinstanzen pro Film zu zählen . Die Anzahl der Instanzen sollte mit der Gesamtzahl der in der Bedingung angegebenen Werte übereinstimmen.

SELECT  a.MovieName
FROM    MovieList a
        INNER JOIN CategoryList b
            ON a.ID = b.MovieID
WHERE   b.CategoryName IN ('Comedy','Romance')
GROUP BY a.MovieName
HAVING COUNT(*) = 2

SQLFiddle Demo (die Antwort)


Trick Nr. 2 ( maximaler Rekord für jeden Eintrag )

Gegebenes Schema,

CREATE TABLE Software
(
    ID INT,
    SoftwareName VARCHAR(25),
    Descriptions VARCHAR(150),
    CONSTRAINT sw_pk PRIMARY KEY (ID),
    CONSTRAINT sw_uq UNIQUE (SoftwareName)  
);

INSERT INTO Software VALUES (1,'PaintMe','used for photo editing');
INSERT INTO Software VALUES (2,'World Map','contains map of different places of the world');
INSERT INTO Software VALUES (3,'Dictionary','contains description, synonym, antonym of the words');

CREATE TABLE VersionList
(
    SoftwareID INT,
    VersionNo INT,
    DateReleased DATE,
    CONSTRAINT sw_uq UNIQUE (SoftwareID, VersionNo),
    CONSTRAINT sw_fk FOREIGN KEY (SOftwareID) REFERENCES Software(ID)
);

INSERT INTO VersionList VALUES (3, 2, '2009-12-01');
INSERT INTO VersionList VALUES (3, 1, '2009-11-01');
INSERT INTO VersionList VALUES (3, 3, '2010-01-01');
INSERT INTO VersionList VALUES (2, 2, '2010-12-01');
INSERT INTO VersionList VALUES (2, 1, '2009-12-01');
INSERT INTO VersionList VALUES (1, 3, '2011-12-01');
INSERT INTO VersionList VALUES (1, 2, '2010-12-01');
INSERT INTO VersionList VALUES (1, 1, '2009-12-01');
INSERT INTO VersionList VALUES (1, 4, '2012-12-01');

FRAGE

Finden Sie die neueste Version für jede Software. Zeigen Sie die folgenden Spalten: SoftwareName, Descriptions, LatestVersion( aus VersionNo Spalte ),DateReleased

Lösung

Einige SQL-Entwickler verwenden fälschlicherweise die MAX()Aggregatfunktion. Sie neigen dazu, so zu kreieren,

SELECT  a.SoftwareName, a.Descriptions,
        MAX(b.VersionNo) AS LatestVersion, b.DateReleased
FROM    Software a
        INNER JOIN VersionList b
            ON a.ID = b.SoftwareID
GROUP BY a.ID
ORDER BY a.ID

SQLFiddle-Demo

(Die meisten RDBMS erzeugen hierzu einen Syntaxfehler, da einige der nicht aggregierten Spalten in der group byKlausel nicht angegeben werden. ) Das Ergebnis erzeugt LatestVersionauf jeder Software das Richtige, aber offensichtlich DateReleasedsind sie falsch. MySQLwird nicht unterstützt Window Functionsund Common Table Expressiondoch wie einige RDBMS bereits. Die Problemumgehung für dieses Problem besteht darin, ein zu erstellen, subquerydas das individuelle Maximum versionNofür jede Software erhält und später für die anderen Tabellen verknüpft wird.

SELECT  a.SoftwareName, a.Descriptions,
        b.LatestVersion, c.DateReleased
FROM    Software a
        INNER JOIN
        (
            SELECT  SoftwareID, MAX(VersionNO) LatestVersion
            FROM    VersionList
            GROUP BY SoftwareID
        ) b ON a.ID = b.SoftwareID
        INNER JOIN VersionList c
            ON  c.SoftwareID = b.SoftwareID AND
                c.VersionNO = b.LatestVersion
GROUP BY a.ID
ORDER BY a.ID

SQLFiddle Demo (die Antwort)


Das war es also. Ich werde bald eine weitere veröffentlichen, sobald ich mich an andere FAQ zum MySQLTag erinnere . Vielen Dank, dass Sie diesen kleinen Artikel gelesen haben. Ich hoffe, dass Sie zumindest noch ein wenig Wissen daraus haben.

UPDATE 1


Trick Nr. 3 ( Finden des neuesten Datensatzes zwischen zwei IDs )

Gegebenes Schema

CREATE TABLE userList
(
    ID INT,
    NAME VARCHAR(20),
    CONSTRAINT us_pk PRIMARY KEY (ID),
    CONSTRAINT us_uq UNIQUE (NAME)  
);

INSERT INTO userList VALUES (1, 'Fluffeh');
INSERT INTO userList VALUES (2, 'John Woo');
INSERT INTO userList VALUES (3, 'hims056');

CREATE TABLE CONVERSATION
(
    ID INT,
    FROM_ID INT,
    TO_ID INT,
    MESSAGE VARCHAR(250),
    DeliveryDate DATE
);

INSERT INTO CONVERSATION VALUES (1, 1, 2, 'hi john', '2012-01-01');
INSERT INTO CONVERSATION VALUES (2, 2, 1, 'hello fluff', '2012-01-02');
INSERT INTO CONVERSATION VALUES (3, 1, 3, 'hey hims', '2012-01-03');
INSERT INTO CONVERSATION VALUES (4, 1, 3, 'please reply', '2012-01-04');
INSERT INTO CONVERSATION VALUES (5, 3, 1, 'how are you?', '2012-01-05');
INSERT INTO CONVERSATION VALUES (6, 3, 2, 'sample message!', '2012-01-05');

FRAGE

Finden Sie die neueste Konversation zwischen zwei Benutzern.

Lösung

SELECT    b.Name SenderName,
          c.Name RecipientName,
          a.Message,
          a.DeliveryDate
FROM      Conversation a
          INNER JOIN userList b
            ON a.From_ID = b.ID
          INNER JOIN userList c
            ON a.To_ID = c.ID
WHERE     (LEAST(a.FROM_ID, a.TO_ID), GREATEST(a.FROM_ID, a.TO_ID), DeliveryDate)
IN
(
    SELECT  LEAST(FROM_ID, TO_ID) minFROM,
            GREATEST(FROM_ID, TO_ID) maxTo,
            MAX(DeliveryDate) maxDate
    FROM    Conversation
    GROUP BY minFROM, maxTo
)

SQLFiddle-Demo


Genial! Eine Einschränkung, John, Ihre erste Lösung funktioniert nur, weil die beiden Felder eine eindeutige Einschränkung aufweisen. Sie hätten eine allgemeinere Lösung verwenden können, um bei einem häufig auftretenden Problem zu helfen. Meiner Meinung nach besteht die einzige Lösung darin, individuelle Auswahlen für comedyund vorzunehmen romance. Havingpasst dann nicht ..
Nawfal

@nawfal nicht wirklich, wenn keine eindeutige Einschränkung hinzugefügt wurde, müssen Sie dann distinctdie have- Klausel SQLFiddle Demo hinzufügen : D
John Woo

63

Teil 2 - Unterabfragen

Okay, jetzt ist der Chef wieder eingebrochen - ich möchte eine Liste aller unserer Autos mit der Marke und insgesamt, wie viele dieser Marken wir haben!

Dies ist eine großartige Gelegenheit, den nächsten Trick in unserer Tasche mit SQL-Extras zu verwenden - die Unterabfrage. Wenn Sie mit dem Begriff nicht vertraut sind, ist eine Unterabfrage eine Abfrage, die in einer anderen Abfrage ausgeführt wird. Es gibt viele verschiedene Möglichkeiten, sie zu verwenden.

Lassen Sie uns für unsere Anfrage zunächst eine einfache Abfrage zusammenstellen, in der jedes Auto und jede Marke aufgelistet sind:

select
    a.ID,
    b.brand
from
    cars a
        join brands b
            on a.brand=b.ID

Wenn wir nur die Anzahl der Autos nach Marke sortieren wollen, können wir natürlich Folgendes schreiben:

select
    b.brand,
    count(a.ID) as countCars
from
    cars a
        join brands b
            on a.brand=b.ID
group by
    b.brand

+--------+-----------+
| brand  | countCars |
+--------+-----------+
| BMW    |         2 |
| Ford   |         2 |
| Nissan |         1 |
| Smart  |         1 |
| Toyota |         5 |
+--------+-----------+

Wir sollten also in der Lage sein, einfach die Zählfunktion zu unserer ursprünglichen Abfrage hinzuzufügen, oder?

select
    a.ID,
    b.brand,
    count(a.ID) as countCars
from
    cars a
        join brands b
            on a.brand=b.ID
group by
    a.ID,
    b.brand

+----+--------+-----------+
| ID | brand  | countCars |
+----+--------+-----------+
|  1 | Toyota |         1 |
|  2 | Ford   |         1 |
|  3 | Nissan |         1 |
|  4 | Smart  |         1 |
|  5 | Toyota |         1 |
|  6 | BMW    |         1 |
|  7 | Ford   |         1 |
|  8 | Toyota |         1 |
|  9 | Toyota |         1 |
| 10 | BMW    |         1 |
| 11 | Toyota |         1 |
+----+--------+-----------+
11 rows in set (0.00 sec)

Nein, das können wir leider nicht. Der Grund dafür ist, dass wir beim Hinzufügen der Fahrzeug-ID (Spalte a.ID) diese zur Gruppe hinzufügen müssen, indem - wenn die Zählfunktion funktioniert, nur eine ID pro ID übereinstimmt.

Hier können wir jedoch eine Unterabfrage verwenden - tatsächlich können wir zwei völlig unterschiedliche Arten von Unterabfragen durchführen, die die gleichen Ergebnisse liefern, die wir dafür benötigen. Die erste besteht darin, die Unterabfrage einfach in die selectKlausel einzufügen . Dies bedeutet, dass jedes Mal, wenn wir eine Datenzeile abrufen, die Unterabfrage ausgeführt wird, eine Datenspalte abruft und diese dann in unsere Datenzeile einfügt.

select
    a.ID,
    b.brand,
    (
    select
        count(c.ID)
    from
        cars c
    where
        a.brand=c.brand
    ) as countCars
from
    cars a
        join brands b
            on a.brand=b.ID

+----+--------+-----------+
| ID | brand  | countCars |
+----+--------+-----------+
|  2 | Ford   |         2 |
|  7 | Ford   |         2 |
|  1 | Toyota |         5 |
|  5 | Toyota |         5 |
|  8 | Toyota |         5 |
|  9 | Toyota |         5 |
| 11 | Toyota |         5 |
|  3 | Nissan |         1 |
|  4 | Smart  |         1 |
|  6 | BMW    |         2 |
| 10 | BMW    |         2 |
+----+--------+-----------+
11 rows in set (0.00 sec)

Und Bam!, Das würde uns tun. Wenn Sie jedoch bemerkt haben, muss diese Unterabfrage für jede einzelne Datenzeile ausgeführt werden, die wir zurückgeben. Selbst in diesem kleinen Beispiel haben wir nur fünf verschiedene Automarken, aber die Unterabfrage wurde elf Mal ausgeführt, da wir elf Datenzeilen haben, die wir zurückgeben. In diesem Fall scheint es also nicht die effizienteste Art zu sein, Code zu schreiben.

Lassen Sie uns für einen anderen Ansatz eine Unterabfrage ausführen und so tun, als wäre es eine Tabelle:

select
    a.ID,
    b.brand,
    d.countCars
from
    cars a
        join brands b
            on a.brand=b.ID
        join
            (
            select
                c.brand,
                count(c.ID) as countCars
            from
                cars c
            group by
                c.brand
            ) d
            on a.brand=d.brand

+----+--------+-----------+
| ID | brand  | countCars |
+----+--------+-----------+
|  1 | Toyota |         5 |
|  2 | Ford   |         2 |
|  3 | Nissan |         1 |
|  4 | Smart  |         1 |
|  5 | Toyota |         5 |
|  6 | BMW    |         2 |
|  7 | Ford   |         2 |
|  8 | Toyota |         5 |
|  9 | Toyota |         5 |
| 10 | BMW    |         2 |
| 11 | Toyota |         5 |
+----+--------+-----------+
11 rows in set (0.00 sec)

Okay, wir haben also die gleichen Ergebnisse (etwas anders geordnet - anscheinend wollte die Datenbank Ergebnisse zurückgeben, die nach der ersten Spalte geordnet sind, die wir dieses Mal ausgewählt haben) - aber die gleichen richtigen Zahlen.

Was ist der Unterschied zwischen den beiden - und wann sollten wir jede Art von Unterabfrage verwenden? Stellen wir zunächst sicher, dass wir verstehen, wie diese zweite Abfrage funktioniert. Wir haben in der fromKlausel unserer Abfrage zwei Tabellen ausgewählt , dann eine Abfrage geschrieben und der Datenbank mitgeteilt, dass es sich tatsächlich um eine Tabelle handelt - mit der die Datenbank vollkommen zufrieden ist. Die Verwendung dieser Methode kann einige Vorteile haben (sowie einige Einschränkungen). In erster Linie wurde diese Unterabfrage einmal ausgeführt . Wenn unsere Datenbank ein großes Datenvolumen enthalten würde, könnte es durchaus zu einer massiven Verbesserung gegenüber der ersten Methode kommen. Da wir dies jedoch als Tabelle verwenden, müssen wir zusätzliche Datenzeilen einbringen, damit diese tatsächlich wieder mit unseren Datenzeilen verknüpft werden können. Wir müssen auch sicher sein, dass es genug gibtDatenzeilen, wenn wir einen einfachen Join wie in der obigen Abfrage verwenden möchten. Wenn Sie sich erinnern, zieht der Join nur Zeilen zurück, die auf beiden Seiten des Joins übereinstimmende Daten enthalten . Wenn wir nicht vorsichtig sind, kann dies dazu führen, dass keine gültigen Daten aus unserer Autotabelle zurückgegeben werden, wenn diese Unterabfrage keine übereinstimmende Zeile enthält.

Wenn wir nun auf die erste Unterabfrage zurückblicken, gibt es auch einige Einschränkungen. Da wir Daten in eine einzelne Zeile zurückziehen , können wir NUR eine Datenzeile zurückziehen. Unterabfragen in der verwendeten selectKlausel einer Abfrage verwenden sehr oft nur eine Aggregatfunktion wie sum, count, maxoder eine andere ähnliche Aggregatfunktion. Sie haben nicht zu haben , aber das ist oft , wie sie geschrieben sind.

Bevor wir fortfahren, werfen wir einen kurzen Blick darauf, wo wir sonst eine Unterabfrage verwenden können. Wir können es in der whereKlausel verwenden - jetzt ist dieses Beispiel ein wenig erfunden, da es in unserer Datenbank bessere Möglichkeiten gibt, die folgenden Daten abzurufen, aber da es sich nur um ein Beispiel handelt, werfen wir einen Blick darauf:

select
    ID,
    brand
from
    brands
where
    brand like '%o%'

+----+--------+
| ID | brand  |
+----+--------+
|  1 | Ford   |
|  2 | Toyota |
|  6 | Holden |
+----+--------+
3 rows in set (0.00 sec)

Dies gibt uns eine Liste von Marken-IDs und Markennamen zurück (die zweite Spalte wird nur hinzugefügt, um uns die Marken anzuzeigen), die den Buchstaben oim Namen enthalten.

Jetzt können wir die Ergebnisse dieser Abfrage in einer where-Klausel verwenden:

select
    a.ID,
    b.brand
from
    cars a
        join brands b
            on a.brand=b.ID
where
    a.brand in
        (
        select
            ID
        from
            brands
        where
            brand like '%o%'
        )

+----+--------+
| ID | brand  |
+----+--------+
|  2 | Ford   |
|  7 | Ford   |
|  1 | Toyota |
|  5 | Toyota |
|  8 | Toyota |
|  9 | Toyota |
| 11 | Toyota |
+----+--------+
7 rows in set (0.00 sec)

Wie Sie sehen können, hatte unsere Autotabelle nur Einträge für zwei von ihnen, obwohl die Unterabfrage die drei Marken-IDs zurückgab.

In diesem Fall funktioniert die Unterabfrage für weitere Details so, als hätten wir den folgenden Code geschrieben:

select
    a.ID,
    b.brand
from
    cars a
        join brands b
            on a.brand=b.ID
where
    a.brand in (1,2,6)

+----+--------+
| ID | brand  |
+----+--------+
|  1 | Toyota |
|  2 | Ford   |
|  5 | Toyota |
|  7 | Ford   |
|  8 | Toyota |
|  9 | Toyota |
| 11 | Toyota |
+----+--------+
7 rows in set (0.00 sec)

Auch hier können Sie sehen, wie eine Unterabfrage im Vergleich zu manuellen Eingaben die Reihenfolge der Zeilen bei der Rückkehr aus der Datenbank geändert hat.

Lassen Sie uns sehen, was wir mit einer Unterabfrage noch tun können, während wir Unterabfragen diskutieren:

  • Sie können eine Unterabfrage in eine andere Unterabfrage einfügen und so weiter und so fort. Es gibt ein Limit, das von Ihrer Datenbank abhängt, aber abgesehen von den rekursiven Funktionen einiger verrückter und verrückter Programmierer werden die meisten Leute dieses Limit niemals erreichen.
  • Sie können eine Reihe von Unterabfragen in eine einzelne Abfrage einfügen, einige in die selectKlausel, einige in die fromKlausel und einige weitere in die whereKlausel. Denken Sie jedoch daran, dass jede von Ihnen eingegebene Abfrage Ihre Abfrage komplexer macht und wahrscheinlich länger dauert ausführen.

Wenn Sie effizienten Code schreiben müssen, kann es hilfreich sein, die Abfrage auf verschiedene Arten zu schreiben und zu sehen (entweder durch Timing oder mithilfe eines Erklärungsplans), welche Abfrage optimal ist, um Ihre Ergebnisse zu erhalten. Der erste Weg, der funktioniert, ist möglicherweise nicht immer der beste Weg, dies zu tun.


Sehr wichtig für neue Entwickler: Unterabfragen werden wahrscheinlich einmal für jedes Ergebnis ausgeführt, es sei denn, Sie können die Unterabfrage als Join verwenden (siehe oben).
Xeoncross

59

Teil 3 - Tricks und effizienter Code

MySQL in () Effizienz

Ich dachte, ich würde ein paar zusätzliche Teile hinzufügen, um Tipps und Tricks zu erhalten.

Eine Frage, die ich ziemlich oft sehe, ist: Wie erhalte ich nicht übereinstimmende Zeilen aus zwei Tabellen und sehe die am häufigsten akzeptierte Antwort wie folgt (basierend auf unserer Tabelle mit Autos und Marken - in der Holden als aufgeführt ist Marke, erscheint aber nicht in der Autotabelle):

select
    a.ID,
    a.brand
from
    brands a
where
    a.ID not in(select brand from cars)

Und ja, es wird funktionieren.

+----+--------+
| ID | brand  |
+----+--------+
|  6 | Holden |
+----+--------+
1 row in set (0.00 sec)

In einigen Datenbanken ist es jedoch nicht effizient. Hier ist ein Link zu einer Frage zum Stapelüberlauf, die danach gefragt wird, und hier ist ein ausgezeichneter ausführlicher Artikel, wenn Sie sich mit dem Thema befassen möchten.

Die kurze Antwort lautet: Wenn der Optimierer nicht effizient damit umgeht, ist es möglicherweise viel besser, eine Abfrage wie die folgende zu verwenden, um nicht übereinstimmende Zeilen zu erhalten:

select
    a.brand
from
    brands a
        left join cars b
            on a.id=b.brand
where
    b.brand is null

+--------+
| brand  |
+--------+
| Holden |
+--------+
1 row in set (0.00 sec)

Aktualisieren Sie die Tabelle mit derselben Tabelle in der Unterabfrage

Ahhh, ein anderer Oldie, aber Goodie - der alte Sie können in der FROM-Klausel keine Zieltabellen -Marken für die Aktualisierung angeben .

Mit MySQL können Sie keine update...Abfrage mit einer Unterauswahl in derselben Tabelle ausführen . Nun denken Sie vielleicht, warum nicht einfach die where-Klausel richtig einfügen? Aber was ist, wenn Sie nur die Zeile mit dem max()Datum unter einer Reihe anderer Zeilen aktualisieren möchten ? In einer where-Klausel können Sie das nicht genau tun.

update 
    brands 
set 
    brand='Holden' 
where 
    id=
        (select 
            id 
        from 
            brands 
        where 
            id=6);
ERROR 1093 (HY000): You can't specify target table 'brands' 
for update in FROM clause

Also können wir das nicht machen, oder? Nun, nicht genau. Es gibt eine hinterhältige Problemumgehung, von der eine überraschend große Anzahl von Benutzern nichts weiß - obwohl sie einige Hackerangriffe enthält, auf die Sie achten müssen.

Sie können die Unterabfrage in eine andere Unterabfrage einfügen, wodurch eine ausreichende Lücke zwischen den beiden Abfragen entsteht, damit sie funktioniert. Beachten Sie jedoch, dass es möglicherweise am sichersten ist, die Abfrage innerhalb einer Transaktion zu speichern. Dadurch wird verhindert, dass andere Änderungen an den Tabellen vorgenommen werden, während die Abfrage ausgeführt wird.

update 
    brands 
set 
    brand='Holden' 
where id=
    (select 
        id 
    from 
        (select 
            id 
        from 
            brands 
        where 
            id=6
        ) 
    as updateTable);

Query OK, 0 rows affected (0.02 sec)
Rows matched: 1  Changed: 0  Warnings: 0

3
Ich möchte nur darauf hinweisen, dass die WHERE NOT EXISTS () -Konstruktion unter dem Gesichtspunkt der Effizienz ziemlich identisch ist, aber meiner Meinung nach viel einfacher zu lesen / zu verstehen ist. Andererseits ist mein Wissen auf MSSQL beschränkt und ich kann nicht schwören, ob dies auch auf anderen Plattformen der Fall ist.
Deroby

Ich habe neulich diese Art von Vergleich versucht, bei der NOT IN () eine Liste mit mehreren hundert IDs hatte und es keinen Unterschied zwischen dieser und der Join-Version der Abfrage gab. Vielleicht macht es einen Unterschied, wenn Sie zu Tausenden oder Milliarden aufstehen.
Buttle Butkus

18

Sie können das Konzept mehrerer Abfragen im Schlüsselwort FROM verwenden. Lassen Sie mich Ihnen ein Beispiel zeigen:

SELECT DISTINCT e.id,e.name,d.name,lap.lappy LAPTOP_MAKE,c_loc.cnty COUNTY    
FROM  (
          SELECT c.id cnty,l.name
          FROM   county c, location l
          WHERE  c.id=l.county_id AND l.end_Date IS NOT NULL
      ) c_loc, emp e 
      INNER JOIN dept d ON e.deptno =d.id
      LEFT JOIN 
      ( 
         SELECT l.id lappy, c.name cmpy
         FROM   laptop l, company c
         WHERE l.make = c.name
      ) lap ON e.cmpy_id=lap.cmpy

Sie können so viele Tabellen verwenden, wie Sie möchten. Verwenden Sie äußere Verknüpfungen und Vereinigungen, wo immer dies erforderlich ist, auch innerhalb von Tabellenunterabfragen.

Dies ist eine sehr einfache Methode, um so viele Tabellen und Felder einzubeziehen.


8

Hoffentlich findet es die Tabellen, während Sie die Sache durchlesen:

jsfiddle

mysql> show columns from colors;                                                         
+-------+-------------+------+-----+---------+----------------+
| Field | Type        | Null | Key | Default | Extra          |
+-------+-------------+------+-----+---------+----------------+           
| id    | int(3)      | NO   | PRI | NULL    | auto_increment |
| color | varchar(15) | YES  |     | NULL    |                |
| paint | varchar(10) | YES  |     | NULL    |                |
+-------+-------------+------+-----+---------+----------------+
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.