GROUP BY collapses many rows into one row per group, and an aggregate function rolls each group up into a single value. It is how a table of individual records becomes a summary.
Where you meet it
- Reports and dashboards — revenue per country, signups per day, orders per customer.
- Counting things — how many users on each plan, how many errors per hour.
- Per-group stats — average order value, highest score, earliest date in each bucket.
What it does
Rows that share the same value in the grouping column fold together into one output row. For each group, an aggregate function reduces a column of many values down to one: COUNT, SUM, AVG, MIN, MAX. The grouping column survives; the individual detail rows do not.
WHERE filters rows before grouping, HAVING filters groups after — and only HAVING may test an aggregate.
How it works
The clauses run in a fixed order. WHERE filters rows before grouping. Then GROUP BY sorts the surviving rows into groups. The aggregate runs per group. Finally HAVING filters the groups after — and it is the only place an aggregate may be tested.
-- table: people(country, plan, spend)
SELECT country,
COUNT(*) AS users, -- one number per group
SUM(spend) AS total,
AVG(spend) AS avg_spend
FROM people
WHERE plan = 'Pro' -- 1. drop rows first
GROUP BY country -- 2. fold rows into groups
HAVING SUM(spend) > 500; -- 3. keep only big-spending groups
Read it group by group: all Pro rows for DE become one row whose COUNT(*) is the row tally, SUM(spend) the total, AVG(spend) the mean — then the same for US, UK, and so on.
Watch out
- No bare columns. Every column in
SELECTmust be inGROUP BYor wrapped in an aggregate. Standard SQL and PostgreSQL reject a stray column; MySQL 8 does too underONLY_FULL_GROUP_BY(the default). COUNT(*)vsCOUNT(col).COUNT(*)counts rows;COUNT(col)counts only rows wherecolis not NULL. They differ whenever NULLs are present.- NULLs are skipped.
SUM,AVG,MIN,MAXignore NULL inputs — soAVGdivides by the non-NULL count, not the row count.
Go deeper
GROUP BY verdichtet viele Zeilen zu einer Zeile pro Gruppe, und eine Aggregatfunktion rollt jede Gruppe zu einem einzigen Wert hoch. So wird aus einer Tabelle einzelner Datensätze eine Auswertung.
Wo es vorkommt
- Reports und Dashboards — Umsatz pro Land, Anmeldungen pro Tag, Bestellungen pro Kunde.
- Zählen — wie viele Nutzer je Tarif, wie viele Fehler pro Stunde.
- Kennzahlen je Gruppe — Ø-Bestellwert, höchster Score, frühestes Datum pro Bucket.
Was es tut
Zeilen mit gleichem Wert in der Gruppierungsspalte klappen zu einer Ausgabezeile zusammen. Für jede Gruppe reduziert eine Aggregatfunktion eine Spalte aus vielen Werten auf einen: COUNT, SUM, AVG, MIN, MAX. Der Gruppenschlüssel bleibt erhalten, die einzelnen Detailzeilen nicht.
WHERE filtert Zeilen vor dem Gruppieren, HAVING filtert Gruppen danach — und nur HAVING darf ein Aggregat prüfen.
Wie es funktioniert
Die Klauseln laufen in fester Reihenfolge. WHERE filtert Zeilen vor dem Gruppieren. Dann sortiert GROUP BY die übrigen Zeilen in Gruppen. Das Aggregat läuft pro Gruppe. Zuletzt filtert HAVING die Gruppen danach — und nur dort darf ein Aggregat geprüft werden.
-- Tabelle: people(country, plan, spend)
SELECT country,
COUNT(*) AS users, -- eine Zahl pro Gruppe
SUM(spend) AS total,
AVG(spend) AS avg_spend
FROM people
WHERE plan = 'Pro' -- 1. erst Zeilen aussortieren
GROUP BY country -- 2. Zeilen in Gruppen falten
HAVING SUM(spend) > 500; -- 3. nur umsatzstarke Gruppen behalten
Lies es Gruppe für Gruppe: alle Pro-Zeilen für DE werden eine Zeile, deren COUNT(*) die Anzahl ist, SUM(spend) die Summe, AVG(spend) der Mittelwert — dann dasselbe für US, UK und so weiter.
Worauf achten
- Keine nackten Spalten. Jede Spalte im
SELECTmuss imGROUP BYstehen oder in einem Aggregat sitzen. Standard-SQL und PostgreSQL lehnen eine freie Spalte ab; MySQL 8 ebenfalls unterONLY_FULL_GROUP_BY(Default). COUNT(*)vs.COUNT(col).COUNT(*)zählt Zeilen;COUNT(col)zählt nur Zeilen, in denencolnicht NULL ist. Bei NULL-Werten ergeben sie Unterschiedliches.- NULL wird übersprungen.
SUM,AVG,MIN,MAXignorieren NULL —AVGteilt also durch die Anzahl der Nicht-NULL-Werte, nicht durch die Zeilenzahl.