SemaTrain Logo Ein Fachportal von SemaTrain

Aggregation: COUNT, SUM, AVG, GROUP BY, HAVING

Jetzt wird aus „Daten abfragen“ echte Analyse: Sie zählen, summieren und gruppieren – und lernen, wann WHERE vs. HAVING richtig ist.

Merksatz: WHERE filtert Zeilen vor der Gruppierung, HAVING filtert Gruppen nach der Aggregation.

Lernziel

Nach dieser Lektion können Sie

SQL Grundlagen Schulung – Kursbezug

Dieses Kapitel ist Teil des Lernpfads zur SQL Grundlagen Schulung. Termine & Buchung laufen über SemaTrain.de.

Fokus: COUNT, SUM, AVG, GROUP BY, HAVING – mit Kurs-/Terminbeispielen.

Worum geht’s?

Lehr-/Lernziele

Merksatz: WHERE = Zeilenfilter, HAVING = Gruppenfilter.

COUNT: Wie viele Termine gibt es?

COUNT(*) – alle Zeilen zählen (SQL)
-- Anzahl aller Termine
SELECT COUNT(*) AS anzahl_termine
FROM termine;
COUNT(*) vs COUNT(spalte)
COUNT(spalte) ignoriert NULL (SQL)
-- COUNT(spalte) zählt NUR nicht-NULL Werte
SELECT
  COUNT(*)          AS alle_zeilen,
  COUNT(trainer_id) AS trainer_gesetzt
FROM termine;

Beispiel: Termine ohne Trainer haben trainer_id = NULL und werden bei COUNT(trainer_id) nicht gezählt.

GROUP BY: Termine pro Ort / Format

GROUP BY ort (SQL)
-- Termine pro Ort
SELECT
  ort,
  COUNT(*) AS termine
FROM termine
GROUP BY ort
ORDER BY termine DESC, ort ASC;
GROUP BY format (SQL)
-- 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)

In eurem Lernpfad liegen Dauer und Preis in termine (dauer_tage, preis_eur). Damit können wir ohne Umwege summieren und mitteln.

AVG: Preis/Dauer pro Format (SQL)
-- Ø-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;
SUM: Umsatz pro Ort (SQL)
-- 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

HAVING COUNT(*) >= 2 (SQL)
-- 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
WHERE vs HAVING (SQL)
-- 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

Typisches Reporting: Wie oft findet welcher Kurs statt – und was ist die Umsatzsumme aus Terminpreisen?

LEFT JOIN + GROUP BY: Termine pro Kurs (SQL)
-- 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;
JOIN + SUM/AVG: Umsatz/Ø-Preis pro Kurs (SQL)
-- 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

Praxisaufgabe

  1. Zähle Termine pro Ort (GROUP BY).
  2. Zeige nur Orte mit mindestens 2 Terminen (HAVING).
  3. Zähle Termine pro Kurs (JOIN + GROUP BY) – nutze LEFT JOIN, damit Kurse ohne Termine sichtbar bleiben.
  4. (Bonus) Berechne Umsatz pro Kurs als SUM(preis_eur).
Lösungsvorschlag anzeigen
Lösung: GROUP BY + HAVING + JOIN (+ Umsatz) (SQL)
-- 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

Primärreferenzen

Geprüfte SQL-Referenzen

Die fachlichen Aussagen dieser Lektion werden gegen Normkontext und zuständige Primärdokumentationen der jeweiligen Datenbanksysteme geprüft. Erklärungen, Beispiele, Übungen, Quizfragen und Lösungen sind eigenständige SemaTrain-Inhalte.

Lektionsprofil: Aggregation, GROUP BY und HAVING

Kursbasis: Gemeinsamer SQL-Kern mit explizit gekennzeichneten Varianten für SQLite 3.53.4, PostgreSQL 18 und MySQL 8.4 LTS.

Quellenprüfung: 2026-09-18

Einordnung: COUNT, SUM, AVG, GROUP BY und HAVING werden im gemeinsamen Kern behandelt; produktspezifische Erweiterungen bleiben gekennzeichnet.

ISO/IEC 9075-1:2023 Information technology — Database languages SQL — Part 1: Framework

Quellentyp: Norm-/Standardreferenz

Projekt / Referenzstelle: ISO/IEC 9075

Ersteller / Maintainer: ISO/IEC JTC 1/SC 32

Versionsbezug: ISO/IEC 9075-1:2023

Aussagebereich: SQL-Framework, Begriffe und Standardkontext.

Offizielle Primärreferenz

SQLite Built-in Aggregate Functions

Quellentyp: Projekt-Primärdokumentation

Projekt / Referenzstelle: SQLite

Ersteller / Maintainer: SQLite Developers

Versionsbezug: SQLite 3.53.4

Aussagebereich: COUNT, SUM, AVG und Aggregatfunktionsverhalten.

Offizielle Primärreferenz

PostgreSQL 18 Table Expressions

Quellentyp: Projekt-Primärdokumentation

Projekt / Referenzstelle: PostgreSQL

Ersteller / Maintainer: PostgreSQL Global Development Group

Versionsbezug: PostgreSQL 18

Aussagebereich: GROUP BY, HAVING und Tabellenexpressions-Pipeline.

Offizielle Primärreferenz

MySQL 8.4 Aggregate Functions

Quellentyp: Hersteller-Primärdokumentation

Projekt / Referenzstelle: MySQL

Ersteller / Maintainer: Oracle Corporation

Versionsbezug: MySQL 8.4 LTS

Aussagebereich: Aggregate Functions, GROUP BY und MySQL-spezifische Regeln.

Offizielle Primärreferenz

SQL Quellen und Methodik

Quiz: Aggregation & GROUP BY

1. (LZ1) Was zählt COUNT(spalte)?

Einfachauswahl – genau eine Antwort ist richtig.

2. (LZ3) Wann nutzt man HAVING statt WHERE?

Einfachauswahl – genau eine Antwort ist richtig.

3. (LZ2) Welche Aussage ist korrekt?

Einfachauswahl – genau eine Antwort ist richtig.

4. (LZ4) Warum ist im Reporting "Termine pro Kurs" oft LEFT JOIN sinnvoll?

Einfachauswahl – genau eine Antwort ist richtig.

Praxisaufgabe

Mini-Projekt: KPI-Board (Aggregation für Reporting)

Sie bauen ein kleines „Dashboard“ aus SQL-Abfragen: Kennzahlen pro Ort, Format und Kurs. Fokus: COUNT/SUM/AVG, GROUP BY und der Unterschied WHERE vs HAVING.

Beitrag zu den Lehr-/Lernzielen: LZ1 (COUNT/SUM/AVG), LZ2 (GROUP BY-Regel), LZ3 (WHERE vs HAVING), LZ4 (JOIN + Aggregation).

Stufe A – Basis-KPIs (ohne JOIN)

Stufe B – WHERE vs HAVING

Stufe C – Reporting pro Kurs (JOIN + GROUP BY)

Bonus – Top-N & Vorfilter

Musterlösung (KPI-Queries)
Projektlösung: KPI-Board (Aggregation) (SQL)
-- ==========================================================
-- Stufe A1: Termine pro Ort
-- ==========================================================
SELECT
  ort,
  COUNT(*) AS termine
FROM termine
GROUP BY ort
ORDER BY termine DESC, ort ASC;

-- ==========================================================
-- Stufe A2: Termine pro Format (Online/Präsenz)
-- ==========================================================
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;

-- ==========================================================
-- Stufe B: HAVING (Gruppenfilter) – nur Orte mit >= 2 Terminen
-- ==========================================================
SELECT
  ort,
  COUNT(*) AS termine
FROM termine
GROUP BY ort
HAVING COUNT(*) >= 2
ORDER BY termine DESC, ort ASC;

-- ==========================================================
-- Stufe B (Vorfilter): WHERE vor GROUP BY (z. B. nur Online)
-- ==========================================================
SELECT
  ort,
  COUNT(*) AS online_termine
FROM termine
WHERE format = 'Online'
GROUP BY ort
ORDER BY online_termine DESC, ort ASC;

-- ==========================================================
-- Stufe C: Termine + Umsatz pro Kurs (JOIN + Aggregation)
-- ==========================================================
SELECT
  k.kurs_id,
  k.kurs_name,
  COUNT(t.termin_id)            AS termine,
  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;

-- ==========================================================
-- Bonus: Top-3 Orte für Online-Termine
-- ==========================================================
SELECT
  ort,
  COUNT(*) AS online_termine
FROM termine
WHERE format = 'Online'
GROUP BY ort
ORDER BY online_termine DESC, ort ASC
LIMIT 3;

Ausblick: Im nächsten Kapitel (DML) ändern Sie Daten (INSERT/UPDATE/DELETE) – und sehen danach, wie sich die KPIs durch neue Termine verändern.