Zum Inhalt springen
SQL Anfänger 30 min

GROUP BY & HAVING

Gruppiere Daten mit GROUP BY und filtere Gruppen mit HAVING - für aussagekräftige Reports und Analysen.

Aktualisiert:

GROUP BY ist eines der maechtigsten Werkzeuge in SQL. Es ermoeglicht dir, Daten zu gruppieren und Aggregatfunktionen auf jede Gruppe einzeln anzuwenden. HAVING filtert dann die Gruppen nach Bedingungen.

Was macht GROUP BY?

Ohne GROUP BY berechnet eine Aggregatfunktion einen Wert fuer alle Zeilen. Mit GROUP BY berechnet sie einen Wert pro Gruppe:

-- OHNE GROUP BY: Ein Durchschnittspreis fuer ALLE Produkte
SELECT ROUND(AVG(preis), 2) AS avg_preis
FROM produkte;
-- Ergebnis: 55.82

-- MIT GROUP BY: Ein Durchschnittspreis PRO Kategorie
SELECT kategorie, ROUND(AVG(preis), 2) AS avg_preis
FROM produkte
GROUP BY kategorie;

Ergebnis:

kategorie   | avg_preis
------------+----------
Accessoires |     27.49
Kleidung    |     66.66
Schuhe      |     79.99

Syntax von GROUP BY

SELECT gruppenspalte, aggregatfunktion(spalte)
FROM tabelle
GROUP BY gruppenspalte;

Wichtige Regel: Jede Spalte im SELECT, die keine Aggregatfunktion ist, muss auch im GROUP BY stehen!

-- FALSCH: 'name' fehlt im GROUP BY
SELECT kategorie, name, AVG(preis)
FROM produkte
GROUP BY kategorie;  -- Fehler!

-- RICHTIG: Alle nicht-aggregierten Spalten im GROUP BY
SELECT kategorie, COUNT(*) AS anzahl, AVG(preis) AS avg_preis
FROM produkte
GROUP BY kategorie;

Praktische Beispiele

Bestellungen pro Kunde

SELECT
    k.name,
    COUNT(b.id) AS anzahl_bestellungen,
    SUM(b.betrag) AS gesamtumsatz
FROM kunden k
LEFT JOIN bestellungen b ON k.id = b.kunden_id
GROUP BY k.name
ORDER BY gesamtumsatz DESC;

Ergebnis:

name          | anzahl_bestellungen | gesamtumsatz
--------------+---------------------+-------------
Anna Schmidt  |                   2 |       199.97
Tom Becker    |                   1 |       184.97
Max Mueller   |                   1 |        94.98
Lisa Weber    |                   1 |        19.99
Sarah Klein   |                   0 |         NULL

Produkte pro Kategorie

SELECT
    kategorie,
    COUNT(*) AS anzahl,
    MIN(preis) AS min_preis,
    MAX(preis) AS max_preis,
    ROUND(AVG(preis), 2) AS avg_preis
FROM produkte
GROUP BY kategorie
ORDER BY anzahl DESC;

Ergebnis:

kategorie   | anzahl | min_preis | max_preis | avg_preis
------------+--------+-----------+-----------+----------
Kleidung    |      3 |     19.99 |    129.99 |     66.66
Accessoires |      2 |     14.99 |     39.99 |     27.49
Schuhe      |      1 |     79.99 |     79.99 |     79.99

Bestellungen pro Monat

-- PostgreSQL:
SELECT
    EXTRACT(YEAR FROM bestelldatum) AS jahr,
    EXTRACT(MONTH FROM bestelldatum) AS monat,
    COUNT(*) AS anzahl,
    SUM(betrag) AS umsatz
FROM bestellungen
GROUP BY EXTRACT(YEAR FROM bestelldatum), EXTRACT(MONTH FROM bestelldatum)
ORDER BY jahr, monat;

-- SQLite:
SELECT
    strftime('%Y', bestelldatum) AS jahr,
    strftime('%m', bestelldatum) AS monat,
    COUNT(*) AS anzahl,
    SUM(betrag) AS umsatz
FROM bestellungen
GROUP BY strftime('%Y', bestelldatum), strftime('%m', bestelldatum)
ORDER BY jahr, monat;

Bestellungen pro Status

SELECT
    status,
    COUNT(*) AS anzahl,
    SUM(betrag) AS umsatz
FROM bestellungen
GROUP BY status
ORDER BY anzahl DESC;

Ergebnis:

status        | anzahl | umsatz
--------------+--------+-------
abgeschlossen |      2 | 199.97
versendet     |      2 | 279.95
offen         |      1 |  19.99

Nach mehreren Spalten gruppieren

Du kannst nach mehreren Spalten gleichzeitig gruppieren:

-- Bestellungen pro Stadt und Status
SELECT
    k.stadt,
    b.status,
    COUNT(*) AS anzahl,
    SUM(b.betrag) AS umsatz
FROM bestellungen b
JOIN kunden k ON b.kunden_id = k.id
GROUP BY k.stadt, b.status
ORDER BY k.stadt, b.status;

Ergebnis:

stadt    | status        | anzahl | umsatz
---------+---------------+--------+-------
Berlin   | abgeschlossen |      2 | 199.97
Berlin   | versendet     |      1 | 184.97
Hamburg  | versendet     |      1 |  94.98
Muenchen | offen         |      1 |  19.99

HAVING - Gruppen filtern

WHERE filtert einzelne Zeilen vor der Gruppierung. HAVING filtert Gruppen nach der Gruppierung.

-- Kunden mit mehr als einer Bestellung
SELECT
    k.name,
    COUNT(b.id) AS anzahl
FROM kunden k
JOIN bestellungen b ON k.id = b.kunden_id
GROUP BY k.name
HAVING COUNT(b.id) > 1;

Ergebnis:

name         | anzahl
-------------+-------
Anna Schmidt |      2

WHERE vs. HAVING

WHEREHAVING
FiltertEinzelne ZeilenGruppen
WannVor GROUP BYNach GROUP BY
Kann Aggregatfunktionen nutzenNeinJa
PositionVor GROUP BYNach GROUP BY
-- WHERE: Filtert vor der Gruppierung
-- HAVING: Filtert nach der Gruppierung
SELECT
    kategorie,
    COUNT(*) AS anzahl,
    ROUND(AVG(preis), 2) AS avg_preis
FROM produkte
WHERE preis > 10              -- Filtert Produkte vor der Gruppierung
GROUP BY kategorie
HAVING AVG(preis) > 30        -- Filtert Gruppen nach der Gruppierung
ORDER BY avg_preis DESC;

Weitere HAVING-Beispiele

-- Kategorien mit einem Durchschnittspreis ueber 50 Euro
SELECT kategorie, ROUND(AVG(preis), 2) AS avg_preis
FROM produkte
GROUP BY kategorie
HAVING AVG(preis) > 50;
-- Kunden mit Gesamtumsatz ueber 100 Euro
SELECT
    k.name,
    SUM(b.betrag) AS gesamtumsatz
FROM kunden k
JOIN bestellungen b ON k.id = b.kunden_id
GROUP BY k.name
HAVING SUM(b.betrag) > 100
ORDER BY gesamtumsatz DESC;
-- Produkte, die mehr als 2 Mal bestellt wurden
SELECT
    p.name,
    SUM(bp.menge) AS gesamt_menge
FROM produkte p
JOIN bestellpositionen bp ON p.id = bp.produkt_id
GROUP BY p.name
HAVING SUM(bp.menge) > 2
ORDER BY gesamt_menge DESC;

Reihenfolge der SQL-Klauseln (komplett)

SELECT spalten                -- 5. Was anzeigen
FROM tabelle                  -- 1. Datenquelle
JOIN tabelle2 ON bedingung    -- 2. Tabellen verknuepfen
WHERE zeilenbedingung         -- 3. Zeilen filtern
GROUP BY gruppenspalte        -- 4. Gruppieren
HAVING gruppenbedingung       -- 5. Gruppen filtern
ORDER BY spalte               -- 6. Sortieren
LIMIT anzahl                  -- 7. Begrenzen

Wichtig: Die Ausfuehrungsreihenfolge unterscheidet sich von der Schreibreihenfolge! SQL fuehrt zuerst FROM aus, dann WHERE, dann GROUP BY usw.

Praxisbeispiel: Verkaufsreport

SELECT
    p.kategorie,
    COUNT(DISTINCT p.id) AS anzahl_produkte,
    SUM(bp.menge) AS verkaufte_stueck,
    ROUND(SUM(bp.menge * bp.einzelpreis), 2) AS umsatz,
    ROUND(AVG(bp.einzelpreis), 2) AS avg_preis_verkauft
FROM produkte p
JOIN bestellpositionen bp ON p.id = bp.produkt_id
GROUP BY p.kategorie
HAVING SUM(bp.menge * bp.einzelpreis) > 50
ORDER BY umsatz DESC;

Was kommt als Naechstes?

Im naechsten Tutorial lernst du Subqueries (Unterabfragen) kennen - damit kannst du Abfragen in Abfragen verschachteln.

Zusammenfassung

  • GROUP BY gruppiert Zeilen und wendet Aggregatfunktionen pro Gruppe an
  • Nicht-aggregierte Spalten im SELECT muessen im GROUP BY stehen
  • HAVING filtert Gruppen (nach der Aggregation)
  • WHERE filtert Zeilen (vor der Aggregation)
  • Du kannst nach mehreren Spalten gruppieren
  • Die Kombination GROUP BY + HAVING + JOINs ermoeglicht komplexe Analysen

Uebungen

  1. Einfach: Zaehle die Anzahl der Kunden pro Stadt.
  2. Aggregation: Zeige fuer jede Kategorie die Anzahl Produkte, den Durchschnittspreis und den Gesamtlagerwert.
  3. HAVING: Finde alle Staedte, aus denen mehr als ein Kunde kommt.
  4. WHERE + HAVING: Zeige Kategorien, in denen Produkte ueber 15 Euro liegen und deren Durchschnittspreis ueber 40 Euro ist.
  5. Komplex: Erstelle einen monatlichen Umsatzreport mit Anzahl Bestellungen, Gesamtumsatz und Durchschnittsbestellwert.
-- Loesung zu Uebung 1:
SELECT stadt, COUNT(*) AS anzahl_kunden
FROM kunden
GROUP BY stadt
ORDER BY anzahl_kunden DESC;

-- Loesung zu Uebung 3:
SELECT stadt, COUNT(*) AS anzahl
FROM kunden
GROUP BY stadt
HAVING COUNT(*) > 1;

Pro-Tipp: Denke an GROUP BY wie an das Sortieren von Karten in Stapel. Zuerst legst du alle Karten mit der gleichen Farbe auf einen Stapel (GROUP BY), dann zaehlst du die Karten in jedem Stapel (COUNT), berechnest den Durchschnitt (AVG) oder findest das Maximum (MAX). HAVING entfernt dann Stapel, die eine Bedingung nicht erfuellen.

Zurück zum SQL Kurs