Grundlagen (SQL)Im Handbuch: SQL Datensätze
image: teaser.webp:contain kind: grundlagen language: SQL reference: [sql, datensaetze]

SQL: Gruppieren und Verbinden

Welche Hauptstadt hat welches Land? Wer sind die Nachbarn Deutschlands? Wo ist Deutsch Amtssprache? Solche Fragen lesen mehrere Tabellen in einer Abfrage. Du gruppierst Zeilen mit GROUP BY, verbindest Tabellen mit JOIN und rechnest Entfernungen auf der Erde aus, an den Ländern der Welt.

1. Vorbereitung

Diese Aktivität setzt die Einführung in SQL fort: SELECT, WHERE, ORDER BY, LIMIT und COUNT solltest du kennen. Öffne den Ordner sql-einfuehrung aus der Einführung mit StrgK StrgO; gibt es ihn noch nicht, legt VS Code ihn an. Öffne dann ein Terminal und maximiere es, dann passen auch längere Ergebnisse hinein.

2. Die ganze Datenbank laden

Lade die ganze Fassung der Länder der Welt herunter und importiere sie in deine Datenbank:

wget https://github.com/specht/workspace-files/raw/main/country-archive/countries.sql
mysql < countries.sql

Das Terminal nach dem Import der ganzen Fassung: sieben Zeilen, eine für jede Tabelle, jede mit dem Status OK.

countries.sql legt sieben Tabellen an. Hast du in der Einführung die kleine Fassung geladen, ersetzt es deren drei Tabellen: Die Länder, Städte und Kontinente sind dieselben, deine Abfragen von dort gehen weiter. Starte mycli wieder und sieh dir die Tabellen an:

SHOW TABLES listet sieben Tabellen: border, city, continent, country, country_language, currency und language.

Die Tabellen
continent(name, area)
name area
Afrika 29648481
Antarktika 13720000
Asien 44614500
… und 4 weitere Zeilen, 7 in allen
country(code, name, population, area, ↑continent, ↑currency_code, tld, calling_code, driving_side)
code name population area ↑continent ↑currency_code tld calling_code driving_side
AFG Afghanistan 41454761 652230 Asien AFN .af +93 right
AGO Angola 36749906 1246700 Afrika AOA .ao +244 right
ALB Albanien 2811655 28748 Europa ALL .al +355 right
… und 194 weitere Zeilen, 197 in allen
city(id, name, ↑country_code, population, latitude, longitude, is_capital)
id name ↑country_code population latitude longitude is_capital
1 Chost AFG 160214 33.3333 69.9167 0
2 Dschalalabad AFG 263312 34.4303 70.4528 0
3 Ghazni AFG 143379 33.5492 68.4233 0
… und 4745 weitere Zeilen, 4748 in allen
border(↑country_code, ↑neighbor_code)neu
↑country_code ↑neighbor_code
AFG CHN
AFG IRN
AFG PAK
… und 633 weitere Zeilen, 636 in allen
currency(code, name)neu
code name
AED VAE-Dirham
AFN Afghani
ALL Albanischer Lek
… und 140 weitere Zeilen, 143 in allen
language(code, name)neu
code name
afr Afrikaans
amh Amharisch
ami Amisisch
… und 180 weitere Zeilen, 183 in allen
country_language(↑country_code, ↑language_code)neu
↑country_code ↑language_code
AFG pus
AFG tuk
AFG prs
… und 369 weitere Zeilen, 372 in allen

Neu in country sind vier Spalten: currency_code, die Währung, ein Fremdschlüssel auf currency; tld, die Internet-Endung wie .de; calling_code, die Vorwahl wie +49; und driving_side, left oder right, je nachdem, auf welcher Straßenseite gefahren wird. border enthält die Grenzen an Land, jede als Paar von zwei Ländern; erst beide Spalten zusammen sind der Primärschlüssel. language und country_language sagen, welche Sprachen in welchem Land Amtssprache sind.

Aufgabe 1Linksverkehr in Europa

In welchen Ländern Europas wird links gefahren? Gib ihre Namen aus.

3. Gruppen bilden: GROUP BY

COUNT(*) hat bisher alle Zeilen gezählt, die übrig blieben. Mit GROUP BY teilt die Datenbank die Zeilen vorher in Gruppen ein und rechnet für jede Gruppe einmal. In wie vielen Ländern wird rechts gefahren, in wie vielen links?

SELECT driving_side, COUNT(*)
FROM country
GROUP BY driving_side;

Zwei Zeilen: right mit 143 Ländern und left mit 54.

Jede Gruppe wird zu einer Zeile. Neben COUNT gibt es SUM (die Summe), AVG (den Durchschnitt), MIN und MAX. Wie viele Städte jedes Land in city hat und wie viele Menschen darin wohnen, die fünf Länder mit den meisten zuerst:

SELECT country_code, COUNT(*) AS cities, SUM(population) AS people
FROM city
GROUP BY country_code
ORDER BY people DESC
LIMIT 5;

Fünf Länder mit der Zahl ihrer Städte und deren Einwohnern: China mit 309 Städten und gut 1,3 Milliarden oben, dann Indien, Brasilien, die Vereinigten Staaten und Japan.

Hinter SELECT dürfen bei GROUP BY nur die Spalten stehen, nach denen gruppiert wird, und Funktionen wie COUNT oder SUM: Welchen name sollte die Datenbank für China auch zeigen, es sind 309 Städte? Gruppen filtern kannst du mit HAVING; anders als WHERE, das einzelne Zeilen prüft, kommt es nach dem Gruppieren. Die Länder mit mehr als 200 Städten:

SELECT country_code, COUNT(*) AS cities
FROM city
GROUP BY country_code
HAVING cities > 200;

Wie die Gruppen entstehen, zeigt der Query Stepper: Er färbt bei GROUP BY jede Gruppe ein, und HAVING streicht ganze Gruppen.

Aufgabe 2Länder pro Kontinent

Wie viele Länder hat jeder Kontinent? Gib für jeden Kontinent seinen Namen und die Zahl seiner Länder aus.

Aufgabe 3Einwohner pro Kontinent

Wie viele Menschen leben auf jedem Kontinent? Gib den Namen und die Summe der Einwohner aus, den Kontinent mit den meisten zuerst.

Aufgabe 4Kontinente mit vielen Ländern

Welche Kontinente haben mehr als 40 Länder? Gib den Namen und die Zahl der Länder aus.

4. Tabellen verbinden: JOIN

In country steht nicht, wie die Hauptstadt eines Landes heißt; das steht in city, bei der Stadt mit is_capital = 1. Ein JOIN setzt die beiden Tabellen zusammen: Zu jeder Zeile von country holt er die Zeilen von city, für die die Bedingung hinter ON stimmt, hier die Städte des Landes.

SELECT country.name, city.name
FROM country
JOIN city ON city.country_code = country.code
WHERE city.is_capital = 1 AND country.continent = 'Europa'
  AND country.population > 50000000;

Die fünf Länder Europas mit mehr als 50 Millionen Einwohnern und ihre Hauptstädte: Deutschland und Berlin, Frankreich und Paris, das Vereinigte Königreich und London, Italien und Rom, Russland und Moskau.

Weil es name in beiden Tabellen gibt, steht der Name der Tabelle davor: country.name, city.name. Die Bedingung hinter ON ist fast immer dieselbe Art: Ein Fremdschlüssel ist gleich dem Primärschlüssel, auf den er verweist. So kommt auch der Name der Währung dazu, über country.currency_code und currency.code.

Wie ein JOIN arbeitet, zeigt der Query Stepper Schritt für Schritt: Nach FROM stehen alle Länder da, nach JOIN ist jedes Land so oft da, wie es Städte hat, und erst WHERE lässt die Hauptstädte übrig.

Aufgabe 5Die Währung Japans

Wie heißt die Währung Japans? Finde ihren Namen in mycli heraus, nicht ihr Kürzel.

Die Nachbarn: dieselbe Tabelle zweimal

In border stehen nur Kürzel. Für die Namen der Nachbarn verbindest du border mit country, und zwar über neighbor_code. Bekommt eine Tabelle mit AS einen eigenen Namen, liest sich das besser:

SELECT neighbor.name
FROM border
JOIN country AS neighbor ON neighbor.code = border.neighbor_code
WHERE border.country_code = 'DEU';

Die neun Nachbarländer Deutschlands, von Belgien bis Tschechien.

Jede Grenze steht in border zweimal, einmal in jeder Richtung: ('DEU', 'FRA') und ('FRA', 'DEU'). Das ist Absicht, sonst müsste die Abfrage in beiden Spalten nach Deutschland suchen.

Aufgabe 6Jede Grenze zweimal

In border steht jede Grenze zweimal, ('DEU', 'FRA') und ('FRA', 'DEU'). Warum?

Jede Grenze zweimal
Aufgabe 7Die Nachbarn Deutschlands

Gib die Namen der Nachbarländer Deutschlands aus.

Aufgabe 8Viele Nachbarn

Welche Länder haben mehr als acht Nachbarn? Gib ihre Namen aus.

Auch ohne Partner: LEFT JOIN

Ein JOIN lässt ein Land weg, für das es keine passende Zeile gibt: Inselstaaten haben keine Nachbarn und tauchen bei einem JOIN mit border gar nicht auf. Ein LEFT JOIN behält jede Zeile der linken Tabelle und füllt die Spalten der rechten mit NULL, wenn es keinen Partner gibt:

SELECT country.name, border.neighbor_code
FROM country
LEFT JOIN border ON border.country_code = country.code
WHERE country.continent = 'Ozeanien' AND country.population > 1000000;

Die drei Länder Ozeaniens mit mehr als einer Million Einwohnern: Australien und Neuseeland mit NULL als Nachbarn, Papua-Neuguinea mit IDN.

Australien und Neuseeland bleiben im Ergebnis, mit NULL; Papua-Neuguinea hat einen Nachbarn, Indonesien auf derselben Insel. Mit WHERE border.country_code IS NULL bleiben die Länder ohne Nachbarn übrig.

Aufgabe 9Ein Kontinent ohne Länder

Welcher Kontinent in continent hat kein einziges Land in country? Gib seinen Namen aus.

Aufgabe 10Die meisten Städte

Welches Land hat die meisten Städte in der Tabelle city? Gib seinen Namen und die Zahl der Städte aus.

5. Viele zu viele: Amtssprachen

Ein Land kann mehrere Amtssprachen haben, Singapur etwa gleich vier, und eine Sprache kann in vielen Ländern Amtssprache sein. Das passt weder in eine Spalte von country noch in eine von language. Deshalb gibt es eine Tabelle dazwischen, country_language, mit einer Zeile für jedes Paar aus Land und Sprache. Für die Namen braucht es zwei JOINs:

SELECT country.name, language.name
FROM country
JOIN country_language ON country_language.country_code = country.code
JOIN language ON language.code = country_language.language_code
WHERE country.name = 'Singapur';

Die vier Amtssprachen Singapurs: Englisch, Hochchinesisch, Malaiisch und Tamil.

Eine solche Tabelle in der Mitte brauchst du immer, wenn auf beiden Seiten viele stehen: bei Filmen und Genres in der Filmdatenbank genauso.

Aufgabe 11Die Amtssprachen der Schweiz

Welche Amtssprachen hat die Schweiz (CHE)? Gib ihre Namen aus.

Aufgabe 12Wo Deutsch Amtssprache ist

In welchen Ländern ist Deutsch Amtssprache? Gib die Namen der Länder aus.

Aufgabe 13Englisch als Amtssprache

In wie vielen Ländern ist Englisch Amtssprache? Finde es in mycli heraus.

6. Wie weit ist es? Entfernungen

Jede Stadt hat in latitude und longitude ihre Lage auf der Erde, Breite und Länge in Grad. Daraus rechnet MySQL die Entfernung zweier Städte aus. POINT(x, y) macht aus zwei Zahlen einen Punkt, und auf der Erde kommt die Länge zuerst, wie auf einer Karte x vor y. ST_Distance_Sphere gibt die Entfernung zweier Punkte auf einer Kugel, so groß wie die Erde, in Metern: die Luftlinie.

Für zwei Städte braucht die Abfrage die Tabelle city zweimal, wie country bei den Nachbarn. CROSS JOIN setzt jede Zeile der einen mit jeder Zeile der anderen zusammen, und WHERE sucht das eine Paar heraus:

SELECT a.name, b.name,
    ST_Distance_Sphere(POINT(a.longitude, a.latitude), POINT(b.longitude, b.latitude)) / 1000 AS km
FROM city AS a
CROSS JOIN city AS b
WHERE a.name = 'Berlin' AND b.name = 'Paris';

Berlin und Paris liegen 876 Kilometer auseinander. Für die Städte in der Nähe von Berlin ist a Berlin und b jede andere Stadt; ON b.id <> a.id lässt das Paar von Berlin mit sich selbst weg. Sortiert nach der Entfernung stehen die nächsten oben:

SELECT b.name, ROUND(ST_Distance_Sphere(POINT(a.longitude, a.latitude), POINT(b.longitude, b.latitude)) / 1000) AS km
FROM city AS a
JOIN city AS b ON b.id <> a.id
WHERE a.name = 'Berlin'
ORDER BY km
LIMIT 5;

Die fünf Städte, die Berlin am nächsten liegen: Potsdam mit 25 Kilometern, dann Magdeburg, Gorzów Wielkopolski, Stettin und Leipzig mit 126 bis 148 Kilometern.

Aufgabe 14In der Nähe von Hamburg

Welche fünf Städte liegen Hamburg am nächsten? Gib ihre Namen aus, die nächste zuerst; die Entfernung darf dabeistehen.

Aufgabe 15Fehlt da nicht eine?

Brandenburg an der Havel liegt nur 60 Kilometer von Berlin entfernt, näher als Magdeburg. Trotzdem steht es nicht unter den fünf Städten, die Berlin am nächsten liegen. Warum?

Fehlt da nicht eine?
Vermutung 16Am anderen Ende der Welt

Welche Stadt in city liegt am weitesten von Berlin entfernt?

Am anderen Ende der Welt
Aufgabe 17Die weiteste Stadt

Prüf deine Vermutung mit einer Abfrage. Gib den Namen der Stadt aus, die am weitesten von Berlin entfernt ist.

Braucht das eine besondere Datenbank?

Für diese Abfragen rechnet MySQL die Entfernung von Berlin zu jeder der 4.748 Städte einmal aus und sortiert. Das dauert ein paar Millisekunden. Eine Karten-App hat aber Millionen Orte, Haltestellen, Restaurants, Ladesäulen, und Tausende fragen in jeder Sekunde: »Was ist in meiner Nähe?« Jedes Mal alle Orte durchzurechnen, wäre zu langsam.

Dafür bekommt die Lage eine eigene Spalte vom Typ POINT und einen SPATIAL INDEX. Der teilt die Karte in Rechtecke ein, große, die kleinere enthalten, und die wieder kleinere (ein R-Baum). Für eine Frage nach der Nähe sieht die Datenbank nur in den Rechtecken nach, die nah genug liegen, und lässt den Rest der Welt aus. Dieselbe Idee steckt in Datenstrukturen wie dem k-d-Baum, der den Raum abwechselnd längs und quer teilt. Für unsere paar Tausend Städte lohnt sich das nicht: Gerechnet ist hier schneller als nachgeschlagen.

7. Zusammenfassung

Baustein Bedeutung
GROUP BY Welche Zeilen bilden zusammen eine Gruppe, für die einmal gerechnet wird?
SUM, AVG, MIN, MAX Was wird für jede Gruppe ausgerechnet?
HAVING Welche Gruppen bleiben übrig?
JOIN … ON Welche Zeilen zweier Tabellen gehören zusammen?
AS bei einer Tabelle Unter welchem Namen steht eine Tabelle in der Abfrage?
LEFT JOIN Welche Zeilen bleiben, auch wenn es keinen Partner gibt?
CROSS JOIN Jede Zeile mit jeder: alle Paare, zum Beispiel aus zwei Städten
ST_Distance_Sphere Wie weit liegen zwei Punkte auf der Erde auseinander, in Metern?

Im Handbuch: