Jetzt wird’s „relational“: Sie verknüpfen Tabellen sauber mit JOINs. Ziel: Termine + Kurse + Trainer in einer Abfrage – verständlich, korrekt und reproduzierbar.
Hinweis: RIGHT JOIN ist nicht in jedem System verfügbar (z. B. SQLite). Oft kann man ihn durch LEFT JOIN „umdrehen“.
SQL Grundlagen Schulung – Kursbezug
Dieses Kapitel ist Teil des Lernpfads zur SQL Grundlagen Schulung.
Termine & Buchung laufen über SemaTrain.de.
Wir arbeiten mit dem Schema: kurse, trainer, termine.
Worum geht’s?
JOIN verknüpft Tabellen über passende Schlüssel (z. B. termine.kurs_id = kurse.kurs_id).
INNER JOIN: nur Treffer auf beiden Seiten.
LEFT JOIN: alles links + Treffer rechts (sonst NULL).
RIGHT JOIN: alles rechts + Treffer links (sonst NULL) – oft ersetzbar durch „umgedrehten“ LEFT JOIN.
Lehr-/Lernziele
(LZ1) Join-Bedingungen korrekt formulieren (FK → PK) und Mehrdeutigkeiten vermeiden.
(LZ2) INNER vs. LEFT vs. RIGHT JOIN unterscheiden und passende Join-Art wählen.
INNER JOIN: A ∩ B (nur passende Paare)
LEFT JOIN: A ⟕ B (alles aus A, B wenn passend, sonst NULL)
RIGHT JOIN: A ⟖ B (alles aus B, A wenn passend, sonst NULL)
Basis: INNER JOIN (Termine + Kursname)
Wir holen zu jedem Termin den Kurstitel – das ist der klassische FK→PK-Join.
INNER JOIN: termine ↔ kurse (SQL)
SELECT
t.termin_id,
t.start_datum,
t.ort,
t.format,
k.kurs_name
FROM termine AS t
INNER JOIN kurse AS k
ON t.kurs_id = k.kurs_id
ORDER BY t.start_datum ASC;
Warum Aliases?
Spalten sind eindeutiger (t.ort statt nur ort).
Abfragen bleiben lesbar, besonders mit vielen Tabellen.
Sie vermeiden „ambiguous column name“-Fehler.
JOIN über 3 Tabellen (Termine + Kurse + Trainer)
Wichtig: INNER JOIN auf trainer lässt Termine ohne Trainer verschwinden.
Wenn Trainer optional ist (wie in eurem Lernpfad), braucht ihr meist LEFT JOIN.
3 Tabellen: INNER vs LEFT (Trainer optional) (SQL)
-- Variante A: Trainer ist Pflicht (Termine ohne Trainer fallen raus)
SELECT
t.termin_id,
t.start_datum,
t.ort,
t.format,
k.kurs_name,
tr.name AS trainer
FROM termine AS t
INNER JOIN kurse AS k ON t.kurs_id = k.kurs_id
INNER JOIN trainer AS tr ON t.trainer_id = tr.trainer_id
ORDER BY t.start_datum ASC;
-- Variante B (praxisnäher): Trainer optional → LEFT JOIN
SELECT
t.termin_id,
t.start_datum,
t.ort,
t.format,
k.kurs_name,
tr.name AS trainer
FROM termine AS t
INNER JOIN kurse AS k ON t.kurs_id = k.kurs_id
LEFT JOIN trainer AS tr ON t.trainer_id = tr.trainer_id
ORDER BY t.start_datum ASC;
LEFT JOIN: wenn rechts optional ist
Typischer Fall: Es gibt einen Termin, aber noch keinen Trainer.
Mit LEFT JOIN behalten Sie die linke Seite komplett.
LEFT JOIN: termine ↔ trainer (optional) (SQL)
-- Alle Termine, Trainer-Name wenn vorhanden (sonst NULL)
SELECT
t.termin_id,
t.start_datum,
t.ort,
t.format,
tr.name AS trainer
FROM termine AS t
LEFT JOIN trainer AS tr
ON t.trainer_id = tr.trainer_id
ORDER BY t.start_datum ASC;
Häufige Falle: WHERE macht LEFT JOIN kaputt
Wenn Sie Bedingungen auf der rechten Tabelle in WHERE setzen, werden NULL-Zeilen rausgefiltert – das wirkt wie ein INNER JOIN.
LEFT JOIN + Filter: WHERE vs ON (SQL)
-- FALLE: wird effektiv zu INNER JOIN, weil tr.name NULL rausfliegt
SELECT t.termin_id, tr.name
FROM termine AS t
LEFT JOIN trainer AS tr
ON t.trainer_id = tr.trainer_id
WHERE tr.name = 'Mathias Ellmann';
-- BESSER: Filter in die JOIN-Bedingung verschieben (NULLs bleiben erhalten)
SELECT t.termin_id, tr.name
FROM termine AS t
LEFT JOIN trainer AS tr
ON t.trainer_id = tr.trainer_id
AND tr.name = 'Mathias Ellmann';
RIGHT JOIN & „Umdrehen“ (LEFT JOIN Ersatz)
RIGHT JOIN ist in manchen Systemen nicht verfügbar (z. B. SQLite).
Sie können ihn meist ersetzen, indem Sie die Tabellen tauschen und LEFT JOIN verwenden.
RIGHT JOIN vs. LEFT JOIN (Ersatz) (SQL)
-- RIGHT JOIN (falls unterstützt): alle Kurse, auch ohne Termin
SELECT
k.kurs_id,
k.kurs_name,
t.termin_id,
t.start_datum
FROM termine AS t
RIGHT JOIN kurse AS k
ON t.kurs_id = k.kurs_id;
-- Ersatz (funktional ähnlich): Tabellen tauschen + LEFT JOIN
SELECT
k.kurs_id,
k.kurs_name,
t.termin_id,
t.start_datum
FROM kurse AS k
LEFT JOIN termine AS t
ON t.kurs_id = k.kurs_id;
Falscher Schlüssel ⇒ doppelte oder fehlende Zuordnungen.
Filter falsch platziert (LEFT JOIN + WHERE auf rechter Tabelle).
Mehrere Treffer (1:n) ⇒ Duplikate sind normal; Aggregation kommt im nächsten Kapitel.
SELECT * bei vielen Tabellen ⇒ unlesbar, ggf. Spaltenkollisionen.
Praxisaufgabe
Geben Sie alle Termine inkl. Kursname aus (INNER JOIN).
Erweitern Sie um Trainername (INNER JOIN auf trainer).
Ändern Sie die Abfrage so, dass Termine ohne Trainer trotzdem erscheinen (LEFT JOIN).
Sortieren Sie nach Startdatum (ASC) und Ort (ASC).
Lösungsvorschlag anzeigen
Lösung: INNER + LEFT JOIN, sortiert (SQL)
SELECT
t.termin_id,
t.start_datum,
t.ort,
t.format,
k.kurs_name,
tr.name AS trainer
FROM termine AS t
INNER JOIN kurse AS k
ON t.kurs_id = k.kurs_id
LEFT JOIN trainer AS tr
ON t.trainer_id = tr.trainer_id
ORDER BY t.start_datum ASC, t.ort ASC;
Kurz-Takeaways
LZ1: JOIN immer über passende Schlüssel (FK→PK) + Aliases.
LZ2: INNER = nur Treffer, LEFT/RIGHT = „alles von einer Seite behalten“.
LZ3: LEFT JOIN + WHERE auf rechter Tabelle ist die Standardfalle.
Mini-Projekt: Termin-Report „Kurse + Trainer“ (JOINs in Stufen)
Sie bauen eine Reporting-Abfrage, die man später als View speichern könnte:
Termine mit Kursname und (optionalem) Trainer.
Fokus: saubere Join-Bedingungen, passende Join-Art und typische Fallen vermeiden.
Beitrag zu den Lehr-/Lernzielen:LZ1 (Join-Bedingung), LZ2 (Join-Art),
LZ3 (Filterfalle), LZ4 (Lesbarkeit/Spaltenwahl).
Stufe A – Basis-Report (Termine + Kursname)
Verbinde termine mit kurse via FK→PK: t.kurs_id = k.kurs_id.
Wähle nur sinnvolle Spalten (kein SELECT *).
Stufe B – Trainer dazu (INNER vs. LEFT)
Erweitere um trainer: t.trainer_id = tr.trainer_id.
Baue zwei Varianten:
INNER JOIN: nur Termine mit Trainer
LEFT JOIN: Termine bleiben, Trainer ggf. NULL (realistischer)
Stufe C – Die Standardfalle (LEFT JOIN + WHERE)
Filter auf rechter Tabelle in WHERE macht LEFT JOIN oft zum INNER JOIN.
Lösung: Filter in die ON-Bedingung verschieben oder nur auf t.* filtern.
Stufe D – Report-Qualität
Sortiere stabil: ORDER BY t.start_datum ASC, t.ort ASC.
Optional: COALESCE(tr.name,'(noch offen)') für „schöne“ Ausgabe.
Bonus – View-ready (nur als Vorschau)
Markiere die finale Query als „view-ready“ (Kapitel 06: CREATE VIEW ...).
Musterlösung anzeigen
Projektlösung: Termin-Report (JOINs in Stufen) (SQL)
-- ==========================================================
-- Stufe A – Basis: Termine + Kursname (INNER JOIN)
-- ==========================================================
SELECT
t.termin_id,
t.start_datum,
t.ort,
t.format,
k.kurs_name
FROM termine AS t
INNER JOIN kurse AS k
ON t.kurs_id = k.kurs_id
ORDER BY t.start_datum ASC, t.ort ASC;
-- ==========================================================
-- Stufe B1 – 3 Tabellen mit INNER JOIN (Trainer ist Pflicht)
-- -> Termine ohne Trainer fallen raus
-- ==========================================================
SELECT
t.termin_id,
t.start_datum,
t.ort,
t.format,
k.kurs_name,
tr.name AS trainer
FROM termine AS t
INNER JOIN kurse AS k ON t.kurs_id = k.kurs_id
INNER JOIN trainer AS tr ON t.trainer_id = tr.trainer_id
ORDER BY t.start_datum ASC, t.ort ASC;
-- ==========================================================
-- Stufe B2 – 3 Tabellen mit LEFT JOIN (Trainer optional)
-- -> Termine bleiben erhalten, Trainer ggf. NULL
-- ==========================================================
SELECT
t.termin_id,
t.start_datum,
t.ort,
t.format,
k.kurs_name,
tr.name AS trainer
FROM termine AS t
INNER JOIN kurse AS k
ON t.kurs_id = k.kurs_id
LEFT JOIN trainer AS tr
ON t.trainer_id = tr.trainer_id
ORDER BY t.start_datum ASC, t.ort ASC;
-- ==========================================================
-- Stufe C – Standardfalle: LEFT JOIN + WHERE auf rechter Tabelle
-- ==========================================================
-- FALLE (macht LEFT JOIN faktisch zu INNER JOIN, weil NULL rausfliegt):
SELECT
t.termin_id,
t.start_datum,
k.kurs_name,
tr.name AS trainer
FROM termine AS t
INNER JOIN kurse AS k
ON t.kurs_id = k.kurs_id
LEFT JOIN trainer AS tr
ON t.trainer_id = tr.trainer_id
WHERE tr.name = 'Mathias Ellmann'
ORDER BY t.start_datum ASC;
-- BESSER (Filter in ON, NULL-Zeilen bleiben erhalten):
SELECT
t.termin_id,
t.start_datum,
k.kurs_name,
tr.name AS trainer
FROM termine AS t
INNER JOIN kurse AS k
ON t.kurs_id = k.kurs_id
LEFT JOIN trainer AS tr
ON t.trainer_id = tr.trainer_id
AND tr.name = 'Mathias Ellmann'
ORDER BY t.start_datum ASC;
-- ==========================================================
-- Stufe D – Report-Qualität: Ausgabe „schön“ machen
-- ==========================================================
SELECT
t.termin_id,
t.start_datum,
t.ort,
t.format,
k.kurs_name,
COALESCE(tr.name, '(noch offen)') AS trainer
FROM termine AS t
INNER JOIN kurse AS k
ON t.kurs_id = k.kurs_id
LEFT JOIN trainer AS tr
ON t.trainer_id = tr.trainer_id
ORDER BY t.start_datum ASC, t.ort ASC;
-- ==========================================================
-- Bonus – View-ready (Kapitel 06): als View speichern (optional)
-- ==========================================================
-- CREATE VIEW v_termin_report AS
-- <Finale Query aus Stufe D (meist ohne ORDER BY)> ;
Ausblick: Im nächsten Kapitel (Aggregation) zählen wir z. B. Termine pro Kurs
oder Orte mit den meisten Terminen – und sehen, warum 1:n-JOINs „Duplikate“ erzeugen können.