SQL Grundlagen Schulung – Kursbezug
Worum geht’s?
- Sie wandeln viele Zeilen in Kenngrößen um: Zählen, Summieren, Mittelwerte.
- GROUP BY definiert die Gruppen (z. B. pro Ort, pro Format, pro Kurs).
- HAVING filtert Gruppen nach Aggregation (z. B. nur Orte mit ≥ 2 Terminen).
Lehr-/Lernziele
- (LZ1) COUNT/SUM/AVG korrekt einsetzen (inkl. COUNT(*) vs COUNT(spalte)).
- (LZ2) GROUP BY-Regel verstehen: Nur gruppierte Spalten oder Aggregationen in SELECT.
- (LZ3) WHERE vs HAVING sauber trennen und korrekt anwenden.
- (LZ4) Aggregationen mit JOIN kombinieren (z. B. Termine pro Kurs, Umsatz pro Kurs).
Merksatz: WHERE = Zeilenfilter, HAVING = Gruppenfilter.
COUNT: Wie viele Termine gibt es?
-- Anzahl aller Termine
SELECT COUNT(*) AS anzahl_termine
FROM termine;COUNT(*) vs COUNT(spalte)
-- COUNT(spalte) zählt NUR nicht-NULL Werte
SELECT
COUNT(*) AS alle_zeilen,
COUNT(trainer_id) AS trainer_gesetzt
FROM termine;GROUP BY: Termine pro Ort / Format
-- Termine pro Ort
SELECT
ort,
COUNT(*) AS termine
FROM termine
GROUP BY ort
ORDER BY termine DESC, ort ASC;-- Termine pro Format (Online/Präsenz)
SELECT
format,
COUNT(*) AS termine
FROM termine
GROUP BY format
ORDER BY termine DESC, format ASC;SUM & AVG: Kennzahlen berechnen (konsistent zum Schema)
-- Ø-Preis und Ø-Dauer pro Format
SELECT
format,
COUNT(*) AS termine,
AVG(preis_eur) AS preis_avg,
AVG(dauer_tage) AS tage_avg
FROM termine
GROUP BY format
ORDER BY termine DESC, format ASC;-- Umsatz (Summe Preis) pro Ort
SELECT
ort,
COUNT(*) AS termine,
SUM(preis_eur) AS umsatz_summe
FROM termine
GROUP BY ort
ORDER BY umsatz_summe DESC, ort ASC;HAVING: Gruppen filtern
-- Nur Orte mit mindestens 2 Terminen
SELECT
ort,
COUNT(*) AS termine
FROM termine
GROUP BY ort
HAVING COUNT(*) >= 2
ORDER BY termine DESC, ort ASC;Häufige Falle: Aggregat in WHERE
-- FALSCH: Aggregatfunktionen in WHERE sind nicht erlaubt
-- SELECT ort, COUNT(*) FROM termine WHERE COUNT(*) >= 2 GROUP BY ort;
-- RICHTIG: Gruppenfilter in HAVING
SELECT
ort,
COUNT(*) AS termine
FROM termine
GROUP BY ort
HAVING COUNT(*) >= 2;Aggregation + JOIN: Termine & Umsatz pro Kurs
-- Termine pro Kurs (inkl. Kurse ohne Termine)
SELECT
k.kurs_id,
k.kurs_name,
COUNT(t.termin_id) AS termine
FROM kurse AS k
LEFT JOIN termine AS t
ON t.kurs_id = k.kurs_id
GROUP BY k.kurs_id, k.kurs_name
ORDER BY termine DESC, k.kurs_name ASC;-- Umsatzsumme pro Kurs (termin-preis_eur wird aggregiert)
-- Hinweis: SUM(NULL) ist NULL, daher COALESCE(...)
SELECT
k.kurs_id,
k.kurs_name,
COUNT(t.termin_id) AS termine,
COALESCE(SUM(t.preis_eur), 0) AS umsatz_summe,
COALESCE(AVG(t.preis_eur), 0) AS preis_avg
FROM kurse AS k
LEFT JOIN termine AS t
ON t.kurs_id = k.kurs_id
GROUP BY k.kurs_id, k.kurs_name
ORDER BY umsatz_summe DESC, k.kurs_name ASC;Warum LEFT JOIN + COUNT(t.termin_id)?
- LEFT JOIN zeigt auch Kurse ohne Termin.
COUNT(t.termin_id)zählt nur echte Termine (NULL nicht).- Für SUM/AVG nutzen wir
COALESCE, damit „0“ statt NULL angezeigt wird.
Typische Stolperfallen
- GROUP BY-Regel: Alles im SELECT muss entweder aggregiert oder gruppiert sein.
- COUNT(*) zählt Zeilen; COUNT(spalte) ignoriert NULL.
- WHERE vs HAVING: erst Zeilen filtern, dann Gruppen filtern.
- JOIN + Aggregation: 1:n erzeugt „Duplikate“ – Aggregation verdichtet wieder.
Praxisaufgabe
- Zähle Termine pro Ort (GROUP BY).
- Zeige nur Orte mit mindestens 2 Terminen (HAVING).
- Zähle Termine pro Kurs (JOIN + GROUP BY) – nutze LEFT JOIN, damit Kurse ohne Termine sichtbar bleiben.
- (Bonus) Berechne Umsatz pro Kurs als SUM(
preis_eur).
Lösungsvorschlag anzeigen
-- 1+2) Orte mit mindestens 2 Terminen
SELECT
ort,
COUNT(*) AS termine
FROM termine
GROUP BY ort
HAVING COUNT(*) >= 2
ORDER BY termine DESC, ort ASC;
-- 3) Termine pro Kurs (inkl. 0)
SELECT
k.kurs_id,
k.kurs_name,
COUNT(t.termin_id) AS termine
FROM kurse AS k
LEFT JOIN termine AS t
ON t.kurs_id = k.kurs_id
GROUP BY k.kurs_id, k.kurs_name
ORDER BY termine DESC, k.kurs_name ASC;
-- Bonus) Umsatz pro Kurs
SELECT
k.kurs_id,
k.kurs_name,
COALESCE(SUM(t.preis_eur), 0) AS umsatz_summe
FROM kurse AS k
LEFT JOIN termine AS t
ON t.kurs_id = k.kurs_id
GROUP BY k.kurs_id, k.kurs_name
ORDER BY umsatz_summe DESC, k.kurs_name ASC;Kurz-Takeaways
- LZ1: COUNT/SUM/AVG sind die Basis für Kennzahlen.
- LZ2: SELECT-Spalten müssen zu GROUP BY/Aggregaten passen.
- LZ3: WHERE (Zeilen) vs HAVING (Gruppen) strikt trennen.
- LZ4: JOIN + GROUP BY = Reporting „pro Kurs/Ort/Format“.