der Unterschied, der Aggregationen entscheidet
Wer HAVING und WHERE als austauschbare Filterklauseln behandelt, produziert Abfragen, die zufaellig funktionieren oder unnoetig langsam laufen. Der Unterschied liegt in der Ausfuehrungsreihenfolge: WHERE filtert vor der Aggregation, HAVING danach, und genau das entscheidet, was ueberhaupt gefiltert werden kann.
Inhaltsverzeichnis
- 1. Warum HAVING vs. WHERE keine Stilfrage ist
- 2. Die logische Ausfuehrungsreihenfolge von SQL
- 3. WHERE: was vor der Aggregation moeglich ist
- 4. HAVING: Filter auf Aggregatfunktionen
- 5. WHERE und HAVING kombiniert einsetzen
- 6. Performance-Unterschied: fruehes vs. spaetes Filtern
- 7. HAVING ohne GROUP BY: der Sonderfall
- 8. Haeufige Fehler in der Praxis
- 9. HAVING und WHERE im direkten Vergleich
- 10. Zusammenfassung
- 11. FAQ
1. Warum HAVING vs. WHERE keine Stilfrage ist
In vielen SQL-Einfuehrungen wird der Unterschied zwischen HAVING vs. WHERE als reine Syntaxregel dargestellt: WHERE steht vor GROUP BY, HAVING danach. Das ist zwar syntaktisch richtig, verschleiert aber den eigentlichen Grund fuer die Existenz zweier separater Klauseln. WHERE und HAVING wirken auf unterschiedlichen Stufen der Abfrageverarbeitung, und diese Stufe entscheidet, welche Ausdruecke ueberhaupt zulaessig sind.
WHERE filtert einzelne Zeilen, bevor irgendeine Gruppierung oder Aggregation stattgefunden hat. HAVING filtert Gruppen, nachdem die Aggregatfunktionen wie SUM, COUNT oder AVG bereits berechnet wurden. Diese Reihenfolge ist keine willkuerliche Sprachkonvention, sondern spiegelt exakt wider, wie eine relationale Datenbank eine Abfrage intern verarbeitet. Wer HAVING vs. WHERE aus dieser Perspektive versteht, vermeidet sowohl Syntaxfehler als auch unnoetig langsame Abfragen.
Der praktische Unterschied wird besonders deutlich, sobald ein Filter auf eine Aggregatfunktion angewendet werden soll, etwa alle Kunden mit mehr als zehn Bestellungen. Dieser Filter kann nicht in WHERE stehen, weil zum Zeitpunkt der WHERE-Auswertung noch keine Bestellungen pro Kunde gezaehlt wurden. Genau fuer diesen Fall existiert HAVING.
2. Die logische Ausfuehrungsreihenfolge von SQL
SQL wird zwar in einer bestimmten Reihenfolge geschrieben, FROM, WHERE, GROUP BY, HAVING, SELECT, ORDER BY, aber intern in einer anderen Reihenfolge ausgewertet. Zuerst wird die Datenquelle in FROM aufgebaut, einschliesslich aller JOINs. Danach filtert WHERE einzelne Zeilen aus dieser Datenquelle heraus. Erst danach gruppiert GROUP BY die verbleibenden Zeilen, und HAVING filtert die entstandenen Gruppen. SELECT waehlt die Ausgabespalten, und ORDER BY sortiert das Endergebnis.
Diese logische Reihenfolge erklaert, warum HAVING vs. WHERE keine austauschbaren Alternativen sind. Ein Ausdruck in WHERE kann sich nur auf Spalten der Basistabellen beziehen, niemals auf das Ergebnis einer Aggregatfunktion, weil diese zum Zeitpunkt der WHERE-Auswertung schlicht noch nicht existiert. HAVING hingegen kann sowohl auf Aggregatfunktionen als auch, in den meisten Datenbanken, auf die Gruppierungsspalten selbst zugreifen.
-- Logical execution order (not the written order):
-- 1. FROM (build the source, including joins)
-- 2. WHERE (filter individual rows)
-- 3. GROUP BY (group remaining rows)
-- 4. HAVING (filter the resulting groups)
-- 5. SELECT (choose output columns)
-- 6. ORDER BY (sort the final result)
SELECT
kunde_id,
COUNT(*) AS anzahl_bestellungen,
SUM(betrag) AS gesamtumsatz
FROM bestellungen
WHERE bestelldatum >= '2026-01-01' -- filters rows before grouping
GROUP BY kunde_id
HAVING COUNT(*) > 10 -- filters groups after aggregation
ORDER BY gesamtumsatz DESC;
Ein haeufiges Missverstaendnis ist die Annahme, ORDER BY koenne sich auf ein in HAVING berechnetes Alias direkt beziehen, ohne dass die Datenbank das erneut auswerten muss. In den meisten Datenbanken funktioniert das tatsaechlich, weil SELECT-Aliase in ORDER BY wiederverwendet werden duerfen, aber nicht in WHERE oder HAVING selbst, da diese Klauseln logisch vor SELECT ausgewertet werden.
3. WHERE: was vor der Aggregation moeglich ist
WHERE arbeitet ausschliesslich auf Zeilenebene und kennt keine Gruppen. Jede Bedingung in WHERE muss sich auf Spaltenwerte einer einzelnen Zeile beziehen, wie sie direkt aus der Tabelle oder einem JOIN stammt. Typische WHERE-Bedingungen sind Datumsfilter, Statusfilter oder Textvergleiche, die unabhaengig von einer spaeteren Gruppierung fuer jede Zeile individuell entschieden werden koennen.
Der entscheidende Vorteil von WHERE gegenueber HAVING: Zeilen, die WHERE ausschliesst, werden nie aggregiert. Das bedeutet weniger Datenvolumen fuer die anschliessende GROUP BY Verarbeitung und damit in der Regel eine schnellere Abfrage. Ein WHERE-Filter auf ein indiziertes Datum kann zudem einen Index-Scan nutzen, was bei einer nachtraeglichen HAVING-Bedingung auf demselben Datum nicht mehr moeglich waere, weil das Datum dann bereits Teil einer aggregierten Gruppe ist.
-- WHERE filters rows BEFORE aggregation — cheap and index-friendly
SELECT
produkt_kategorie,
COUNT(*) AS anzahl_verkaeufe,
SUM(menge) AS gesamtmenge
FROM verkaeufe
WHERE verkaufsdatum >= '2026-06-01'
AND status = 'abgeschlossen'
GROUP BY produkt_kategorie;
-- Cannot be done in WHERE, since COUNT(*) does not exist yet at this stage:
-- WHERE COUNT(*) > 100 -- syntax error in every major database
4. HAVING: Filter auf Aggregatfunktionen
HAVING existiert genau fuer den Fall, dass ein Filter sich auf das Ergebnis einer Aggregatfunktion bezieht, nicht auf einzelne Zeilenwerte. Typische Beispiele sind Kunden mit mehr als einer bestimmten Anzahl Bestellungen, Produktkategorien mit einem Umsatz ueber einer Schwelle, oder Regionen mit einer durchschnittlichen Bestellgroesse unterhalb eines Grenzwerts. All diese Bedingungen lassen sich erst formulieren, nachdem SUM, COUNT oder AVG pro Gruppe berechnet wurden.
In HAVING koennen sowohl Aggregatfunktionen als auch, in den meisten Datenbanken einschliesslich PostgreSQL und MySQL, die Gruppierungsspalten selbst referenziert werden. Ein Filter wie HAVING region = 'Nord' AND SUM(umsatz) > 10000 ist vollkommen gueltig, auch wenn die reine Spaltenbedingung region = 'Nord' genauso gut, und performanter, in WHERE stehen koennte, weil sie keine Aggregatfunktion benoetigt.
-- HAVING filters on the result of aggregate functions
SELECT
region,
kategorie,
SUM(umsatz) AS summe_umsatz,
AVG(bestellwert) AS durchschnitt_bestellwert
FROM verkaeufe
GROUP BY region, kategorie
HAVING SUM(umsatz) > 10000
AND AVG(bestellwert) < 500;
-- HAVING can also reference the grouping column directly,
-- but a plain column condition belongs in WHERE for performance reasons
Ein oft uebersehener Punkt: HAVING kann eine Bedingung ueber ein SELECT-Alias referenzieren, wenn die Datenbank das unterstuetzt, etwa PostgreSQL und MySQL, waehrend SQL Server und Oracle strikter sind und den vollen Ausdruck in HAVING erneut fordern. Diese Inkonsistenz zwischen Datenbanken ist ein guter Grund, bei portablem SQL den vollen Aggregatausdruck in HAVING zu wiederholen, statt sich auf Alias-Unterstuetzung zu verlassen.
5. WHERE und HAVING kombiniert einsetzen
In der Praxis werden WHERE und HAVING fast immer gemeinsam eingesetzt, jeweils fuer den Teil des Problems, fuer den sie gebaut sind. WHERE reduziert zuerst die Datenmenge auf die relevanten Zeilen, etwa einen bestimmten Zeitraum oder einen bestimmten Status. Danach gruppiert GROUP BY die verbleibenden Zeilen, und HAVING filtert die Gruppen nach dem aggregierten Ergebnis. Diese Kombination ist der Normalfall in nahezu jeder Reporting-Abfrage.
Ein haeufiger Fehler ist, eine Bedingung, die eigentlich in WHERE gehoert, versehentlich in HAVING zu platzieren, weil sie in derselben Abfrage neben einer echten HAVING-Bedingung steht. Das Ergebnis ist zwar oft trotzdem korrekt, aber die Performance leidet, weil alle Zeilen erst aggregiert werden, bevor der eigentlich fruehe Filter greift. Die Faustregel: Alles, was sich rein auf Zeilenwerte bezieht, gehoert in WHERE, alles, was eine Aggregatfunktion braucht, gehoert in HAVING.
-- WRONG: row-level condition placed in HAVING — filters after aggregation
SELECT region, SUM(umsatz) AS summe
FROM verkaeufe
GROUP BY region
HAVING region IN ('Nord', 'Sued') AND SUM(umsatz) > 5000;
-- RIGHT: row-level condition moved to WHERE — filters before aggregation
SELECT region, SUM(umsatz) AS summe
FROM verkaeufe
WHERE region IN ('Nord', 'Sued')
GROUP BY region
HAVING SUM(umsatz) > 5000;
6. Performance-Unterschied: fruehes vs. spaetes Filtern
Der Performance-Unterschied zwischen HAVING vs. WHERE ergibt sich unmittelbar aus der Ausfuehrungsreihenfolge. Ein WHERE-Filter reduziert die Zeilenmenge, bevor die teure Aggregation ueberhaupt beginnt. Ein HAVING-Filter dagegen wird erst angewendet, nachdem alle Zeilen gruppiert und aggregiert wurden, selbst wenn am Ende viele Gruppen wieder verworfen werden. Auf grossen Tabellen kann dieser Unterschied den Ausfuehrungsplan erheblich veraendern.
Moderne Query-Optimierer koennen manche row-level Bedingungen aus HAVING automatisch in ein aequivalentes WHERE umschreiben, sogenanntes Predicate Pushdown. Verlassen sollte man sich darauf aber nicht, da diese Optimierung nicht in jeder Datenbank und nicht bei jedem Ausdruck greift. Ein expliziter WHERE-Filter ist immer die sicherere und lesbarere Variante, unabhaengig davon, ob der Optimierer die Umschreibung ohnehin vornehmen wuerde.
Bei sehr grossen Faktentabellen im Reporting-Kontext kann der Unterschied zwischen einem fruehen WHERE-Filter auf ein indiziertes Datum und einem spaeten, aequivalenten HAVING-Filter mehrere Sekunden Ausfuehrungszeit ausmachen, insbesondere wenn die Gruppierung selbst rechenintensiv ist, etwa bei vielen unterschiedlichen Gruppierungsschluesseln.
7. HAVING ohne GROUP BY: der Sonderfall
Ein weniger bekannter Fall: HAVING kann auch ohne explizites GROUP BY verwendet werden. In diesem Fall behandelt die Datenbank die gesamte Ergebnismenge als eine einzige Gruppe, und HAVING filtert, ob diese eine Gruppe insgesamt in das Ergebnis aufgenommen wird oder nicht. Das ist nuetzlich, um zu pruefen, ob eine Aggregatbedingung ueber die gesamte Tabelle erfuellt ist, etwa ob der Gesamtumsatz eine Schwelle uebersteigt.
-- HAVING without GROUP BY: the whole result set is treated as one group
SELECT SUM(umsatz) AS gesamtumsatz
FROM verkaeufe
WHERE verkaufsdatum >= '2026-01-01'
HAVING SUM(umsatz) > 1000000;
-- Returns either one row (condition met) or zero rows (condition not met)
Dieser Sonderfall wird selten genutzt, ist aber in Monitoring- oder Alerting-Abfragen praktisch, bei denen ein leeres Ergebnis als Signal dient, dass eine Schwelle nicht erreicht wurde. Anwendungscode, der auf ein leeres Ergebnis prueft, statt einen numerischen Wert zu vergleichen, kann so einfacher gebaut werden, insbesondere in Systemen, die ohnehin nur den Vorhandensein-Status einer Abfrage auswerten.
8. Haeufige Fehler in der Praxis
Der haeufigste Fehler bei HAVING vs. WHERE ist der Versuch, eine Aggregatfunktion in WHERE zu verwenden, was in jeder relationalen Datenbank zu einem Syntaxfehler fuehrt, da die Aggregatfunktion zum Zeitpunkt der WHERE-Auswertung schlicht nicht existiert. Der zweite haeufige Fehler ist das Gegenteil: eine reine Spaltenbedingung wird in HAVING platziert, was zwar syntaktisch korrekt ist, aber unnoetig Performance kostet, weil die Filterung erst nach der Aggregation stattfindet.
Ein dritter, subtilerer Fehler betrifft NULL-Werte in Aggregatfunktionen. HAVING SUM(spalte) > 0 verhaelt sich anders als erwartet, wenn alle Werte in einer Gruppe NULL sind, da SUM in diesem Fall NULL zurueckgibt, nicht 0, und ein Vergleich mit NULL in SQL weder wahr noch falsch, sondern unbekannt ist. Die Gruppe wird dann stillschweigend aus dem Ergebnis entfernt, was bei fehlender Kenntnis der NULL-Semantik zu schwer nachvollziehbaren fehlenden Zeilen fuehrt.
| Kriterium | WHERE | HAVING |
|---|---|---|
| Wirkungsebene | Einzelne Zeilen, vor Aggregation | Gruppen, nach Aggregation |
| Aggregatfunktionen erlaubt | Nein | Ja |
| Ohne GROUP BY nutzbar | Ja, Standardfall | Ja, ganze Tabelle als eine Gruppe |
| Index-Nutzung | Direkt moeglich | Nicht auf Aggregatergebnis |
| Performance-Effekt | Reduziert Daten frueh | Filtert nach teurer Aggregation |
Mironsoft
SQL-Reporting, Datenmodellierung und Query-Optimierung
Langsame Reporting-Queries mit falsch platzierten Filtern?
Wir pruefen bestehende Abfragen auf falsch platzierte WHERE- und HAVING-Bedingungen und bauen Reporting-SQL, das fruehestmoeglich filtert und erst spaeter aggregiert.
Query-Review
HAVING- und WHERE-Bedingungen auf Effizienz pruefen
Performance-Tuning
Ausfuehrungsplaene analysieren und Filter frueh platzieren
Reporting-Aufbau
Neue Dashboards mit sauberen Aggregations-Queries entwerfen
10. Zusammenfassung
Der Unterschied zwischen HAVING vs. WHERE ergibt sich direkt aus der logischen Ausfuehrungsreihenfolge von SQL: WHERE filtert Zeilen, bevor GROUP BY gruppiert und Aggregatfunktionen berechnet werden, HAVING filtert die entstandenen Gruppen danach. Aggregatfunktionen wie SUM, COUNT und AVG koennen deshalb nur in HAVING referenziert werden, niemals in WHERE. Reine Spaltenbedingungen gehoeren dagegen immer in WHERE, selbst wenn sie syntaktisch auch in HAVING stehen koennten, weil sie dort die Aggregation unnoetig verzoegern.
Die Kombination beider Klauseln ist der Normalfall in Reporting-Abfragen: WHERE reduziert fruehzeitig die Datenmenge, HAVING filtert das aggregierte Ergebnis nach fachlichen Kriterien wie Mindestumsatz oder Mindestbestellanzahl. Wer diese Trennung konsequent einhaelt, schreibt Abfragen, die sowohl korrekt als auch performant sind, unabhaengig von der zugrunde liegenden Datenbank.
HAVING vs. WHERE — Das Wichtigste auf einen Blick
WHERE
Filtert einzelne Zeilen vor der Aggregation, keine Aggregatfunktionen erlaubt, nutzt Indizes direkt.
HAVING
Filtert Gruppen nach der Aggregation, Aggregatfunktionen und Gruppierungsspalten erlaubt.
Faustregel
Reine Spaltenbedingung: WHERE. Bedingung auf Aggregatfunktion: HAVING.
NULL-Falle
SUM() ueber ausschliesslich NULL-Werte liefert NULL, nicht 0, ein Vergleich in HAVING scheitert dann stillschweigend.