Stolperfallen bei Joins
Joins sind mächtig, und darum sind ihre Fehler gefährlich: Die meisten geben keine Fehlermeldung, sondern nur ein falsches Ergebnis, das glaubhaft aussieht. Hier sind die drei häufigsten.
1. Ein vergessenes ON. SQLite lässt dich JOIN ohne Bedingung schreiben. Dann sagt nichts, welche Zeilen zusammengehören, und es paart jede Zeile der linken Tabelle mit jeder der rechten:
SELECT COUNT(*) AS anzahl_zeilen
FROM kunden AS k
JOIN bestellungen AS b;| anzahl_zeilen |
|---|
| 420 |
14 Kunden mal 30 Bestellungen: 420 Zeilen, jede mögliche Kombination. Ein solcher Join heißt Cross Join. Manchmal ist er das, was du willst (zum Beispiel jeder Kunde mit jedem Spiel), aber meistens ist es ein vergessenes ON. Wenn ein Ergebnis viel mehr Zeilen hat, als du erwartet hast, prüfe das ON jedes Joins. Du triffst vielleicht auch auf eine ältere Art zu verbinden, mit den durch Kommas getrennten Tabellen und der Bedingung in WHERE: FROM kunden k, bestellungen b WHERE b.kunde_id = k.id. Vergisst du dort das WHERE, bekommst du dieselben 420 Zeilen.
2. Zeilen, die sich vervielfachen. Ein Join gibt eine Zeile für jedes Paar. Wenn du zwei Tabellen auf einmal mit den Spielen verbindest, etwa die Bewertungen und die Positionen, wird jede Bewertung eines Spiels mit jeder seiner Positionen gepaart. Dragon Forge hat 4 Bewertungen und war in 4 Bestellungen:
SELECT s.titel, COUNT(bw.sterne) AS anzahl_bewertungen, SUM(p.menge) AS exemplare
FROM spiele AS s
JOIN bewertungen AS bw ON bw.spiel_id = s.id
JOIN positionen AS p ON p.spiel_id = s.id
WHERE s.id = 1
GROUP BY s.id, s.titel;| titel | anzahl_bewertungen | exemplare |
|---|---|---|
| Dragon Forge | 16 | 16 |
16 Bewertungen und 16 Exemplare, viermal zu viele: Jede der 4 Bewertungen traf auf jede der 4 Positionen, und das macht 16 Zeilen. Nichts verrät dir, dass etwas nicht stimmt; die Zahlen sind nur zu groß. Eine Zählung lässt sich mit COUNT(DISTINCT …) eines Schlüssels retten: Jede Bewertung gehört einem Kunden (der Schlüssel von bewertungen ist das Spiel und der Kunde), also zählt COUNT(DISTINCT bw.kunde_id) jede Bewertung einmal, wie oft ihre Zeile auch wiederholt wurde. Eine Summe lässt sich so nicht retten, denn zwei verschiedene Positionen können dieselbe Menge haben. Hole sie mit einer eigenen Abfrage oder mit einer Unterabfrage, die du in der nächsten Lektion lernst.
3. Eine Bedingung in ON oder in WHERE. Bei JOIN ist es egal, ob eine Bedingung in ON oder in WHERE steht. Bei LEFT JOIN nicht. Wie viele Bewertungen mit fünf Sternen hat jedes Spiel?
SELECT s.titel, COUNT(bw.sterne) AS fuenf_sterne
FROM spiele AS s
LEFT JOIN bewertungen AS bw ON bw.spiel_id = s.id
WHERE bw.sterne = 5
GROUP BY s.id, s.titel
ORDER BY s.titel;| titel | fuenf_sterne |
|---|---|
| Cube Logic | 1 |
| Dragon Forge | 2 |
| Knight's Oath | 1 |
| … | … |
Die Spiele ohne Fünf-Sterne-Bewertung fehlen, obwohl LEFT JOIN genau dafür da war. WHERE läuft nach dem Join: Die Zeilen mit NULL und die Zeilen der anderen Bewertungen bestehen bw.sterne = 5 nicht, und WHERE wirft sie hinaus, samt der Spiele. Die Bedingung gehört in ON:
SELECT s.titel, COUNT(bw.sterne) AS fuenf_sterne
FROM spiele AS s
LEFT JOIN bewertungen AS bw ON bw.spiel_id = s.id AND bw.sterne = 5
GROUP BY s.id, s.titel
ORDER BY s.titel;| titel | fuenf_sterne |
|---|---|
| Bakery Story | 0 |
| Castle Siege | 0 |
| Cube Logic | 1 |
| … | … |
Eine Bedingung in ON entscheidet, welche Zeilen der rechten Tabelle als Gegenstück zählen, und LEFT JOIN behält trotzdem jede Zeile der linken Tabelle: Ein Spiel ohne Fünf-Sterne-Bewertung bleibt, mit NULL, und COUNT gibt ihm 0. Bei LEFT JOIN kommt also eine Bedingung an die rechte Tabelle in ON und eine Bedingung an die linke Tabelle in WHERE.
So schreibst du es:
Anweisungen
- 1
Jede der drei Abfragen im Editor enthält eine der Stolperfallen, und keine endet mit einem Fehler. Die erste soll die Bewertungen jedes Spiels zählen, sagt aber, dass jedes Spiel 25 hat. Füge die fehlende Bedingung hinzu, damit sie nur die Bewertungen des jeweiligen Spiels zählt. Mit JOIN fehlen die Spiele ohne Bewertung im Ergebnis, und das ist hier in Ordnung:
titel anzahl_bewertungen Bakery Story 1 Cube Logic 2 Dragon Forge 4 … … - 2
Die zweite soll die Anzahl der Bewertungen und die Anzahl der Bestellungen jedes Spiels zeigen, aber einige Zahlen sind zu groß: Dragon Forge hat 4 Bewertungen und 4 Bestellungen, nicht 16. Korrigiere das Zählen mit DISTINCT, sodass jede Bewertung und jede Bestellung einmal gezählt wird:
titel anzahl_bewertungen anzahl_bestellungen Bakery Story 1 3 Castle Siege 0 1 Cube Logic 2 3 Dragon Forge 4 4 … … … - 3
Die dritte soll zeigen, wie viele Bestellungen jeder Kunde 2024 aufgegeben hat, mit 0 für die Kunden, die keine aufgegeben haben, aber diese Kunden fehlen. Verschiebe die Bedingung dorthin, wo sie hingehört:
name bestellungen_2024 Anna Fischer 1 Emma Becker 3 Greta Hoffmann 0 Hannes Schulz 0 … …