GROUP BY fasst Zeilen zu Gruppen zusammen
Aggregatfunktionen wie COUNT, SUM und AVG verdichten viele Zeilen zu einer einzigen Zahl. Mit GROUP BY kannst du das pro Kategorie machen — also eine Zahl pro Kunde, pro Monat oder pro Status. Jeder eindeutige Wert (oder jede Kombination von Werten) landet als eine Zeile im Ergebnis.
Drei Kunden, drei Zeilen im Ergebnis. Die ursprünglichen sechs Zeilen sind verschwunden — sie wurden zu Buckets pro Kunde zusammengefasst, wobei COUNT(*) und SUM(amount) jeweils innerhalb einer Gruppe berechnet werden.
Das gedankliche Modell dahinter: GROUP BY customer sagt im Grunde „behandle alle Zeilen mit demselben Kunden als eine Gruppe". Die Aggregatfunktionen arbeiten dann getrennt auf jeder dieser Gruppen.
Was darf in die SELECT-Liste?
Genau hier stolpern viele. Sobald du GROUP BY benutzt, muss jede Spalte in der SELECT-Liste entweder auch im GROUP BY stehen oder innerhalb einer Aggregatfunktion auftauchen. Sonst wäre der Wert mehrdeutig — aus welcher Zeile der Gruppe sollte er denn kommen?
Hättest du SELECT region, rep, SUM(amount) mit GROUP BY region geschrieben, würde SQLite das brav ausführen (im Gegensatz zu anderen Datenbanken, die hier streng sind), aber rep würde willkürlich aus der Gruppe gewählt. Du bekämst pro Region einen Namen, ohne Garantie, welcher es ist. Verlass dich nicht darauf – gruppiere immer nach jeder nicht aggregierten Spalte, die du ausgibst.
SQLite HAVING: Gruppen nach der Aggregation filtern
WHERE filtert Zeilen vor der Gruppierung, HAVING filtert Gruppen danach. Das ist der ganze Unterschied zwischen WHERE und HAVING in SQLite – und genau deshalb kannst du COUNT(*) > 1 nicht in eine WHERE-Klausel packen: Zum Zeitpunkt der WHERE-Auswertung existiert der Count schlicht noch nicht.
Cleo hat nur eine einzige Bestellung aufgegeben, deshalb fliegt ihre Gruppe raus. Übrig bleiben Ada und Boris. Die Bedingung prüft also den aggregierten Wert pro Gruppe, nicht die einzelnen Zeilen.
Übrigens: Du kannst Spaltenaliase aus dem SELECT direkt in HAVING verwenden – SQLite lässt das zu:
Das ist meist lesbarer, als SUM(amount) in der HAVING-Klausel nochmal zu wiederholen.
WHERE vs. HAVING in SQLite: beides clever kombinieren
Die beiden Klauseln sind kein Entweder-oder. WHERE schränkt ein, welche Zeilen überhaupt in die Gruppierung einfließen; HAVING filtert anschließend, welche Gruppen es ins Ergebnis schaffen. In der Praxis nutzt man fast immer beides zusammen.
Lies die Query einfach in der Reihenfolge, in der sie ausgeführt wird:
WHERE status = 'paid'— alle stornierten Zeilen fliegen komplett raus.GROUP BY customer— der Rest wird pro Kunde in Gruppen gepackt.SUM(amount)läuft je Gruppe einmal.HAVING SUM(amount) > 75— nur Gruppen, die diese Bedingung erfüllen, bleiben übrig.
Boris (80 + 20 = 100) und Cleo (200) bleiben drin. Adas einzige bezahlte Bestellung war 50 und reißt damit die Schwelle nicht.
Mehrere Bedingungen und mehrere Gruppierungs-Spalten
HAVING versteht dieselben booleschen Operatoren wie WHERE — also AND, OR und NOT — und du kannst nach mehreren Spalten gleichzeitig gruppieren, um feinere Unter-Gruppen zu bekommen:
Jedes Paar (region, quarter) bildet eine eigene Gruppe. Die HAVING-Klausel verlangt sowohl eine Summe über 100 als auch mindestens zwei Abschlüsse. Damit bleiben nur ('North', 'Q1') und ('South', 'Q2') übrig.
Praxisbeispiel: Duplikate finden
Die Kombination GROUP BY ... HAVING COUNT(*) > 1 ist der Klassiker, um doppelte Werte in einer Spalte aufzuspüren:
Es tauchen zwei Duplikate auf. Ab hier würdest du normalerweise entscheiden, ob du die Konten zusammenführst, einen UNIQUE-Constraint hinzufügst oder die Daten bereinigst – aber das Abfragemuster zum Aufspüren bleibt immer dasselbe.
HAVING ohne GROUP BY
Ungewöhnlich, aber erlaubt. Ohne GROUP BY wird die gesamte Ergebnismenge als eine einzige Gruppe behandelt, und HAVING filtert sie als Ganzes – du bekommst also entweder alle aggregierten Werte oder gar nichts:
Die einzelne Ergebniszeile erscheint, weil die Summe 160 beträgt. Setzt du den Schwellwert auf > 200, liefert die Abfrage gar keine Zeilen mehr zurück. In der Praxis wirst du HAVING fast immer zusammen mit GROUP BY einsetzen – trotzdem ist es gut zu wissen, dass die Sprache das nicht zwingend voraussetzt.
Kurz zusammengefasst
GROUP BYfasst Zeilen zu Gruppen pro Schlüssel zusammen; Aggregatfunktionen laufen innerhalb jeder Gruppe.- Jede nicht-aggregierte Spalte im
SELECTsollte auch imGROUP BYauftauchen. WHEREfiltert Zeilen vor der Gruppierung,HAVINGfiltert Gruppen danach.- Aggregate wie
COUNT(*)undSUM(...)gehören inHAVING, niemals inWHERE. HAVINGakzeptiert verknüpfte Bedingungen und kann auf Aliase aus demSELECTzugreifen.
Als Nächstes: Fremdschlüssel
Eine einzelne Tabelle zu aggregieren ist praktisch, aber in echten Schemas verteilen sich die Daten meist über mehrere Tabellen – hier die Bestellungen, dort die Kunden, woanders die Produkte. Fremdschlüssel sind das Mittel, mit dem du diese Tabellen sauber miteinander verknüpfst, sodass die Beziehungen konsistent bleiben. Genau darum geht es im nächsten Kapitel.
Häufig gestellte Fragen
Was ist der Unterschied zwischen WHERE und HAVING in SQLite?
WHERE filtert einzelne Zeilen vor dem Gruppieren. HAVING filtert Gruppen nach der Aggregation. WHERE amount > 100 lässt also nur Zeilen über 100 durch, während HAVING SUM(amount) > 100 nur Gruppen behält, deren Summe über 100 liegt. Aggregatfunktionen wie COUNT oder SUM darfst du in WHERE nicht verwenden – genau dafür gibt es HAVING.
Kann man HAVING in SQLite ohne GROUP BY verwenden?
Ja. Ohne GROUP BY behandelt SQLite die gesamte Ergebnismenge als eine einzige Gruppe, und HAVING filtert diese Gruppe als Ganzes. Das Query liefert dann entweder genau eine Zeile oder gar keine. In der Praxis sieht man das selten – meistens gehört zu einem HAVING auch ein GROUP BY.
Wie filtere ich Gruppen in SQLite nach COUNT?
Das Aggregat gehört in HAVING, nicht in WHERE. Beispiel: SELECT customer_id, COUNT(*) FROM orders GROUP BY customer_id HAVING COUNT(*) > 1 liefert alle Kunden mit mehr als einer Bestellung. In SQLite darfst du innerhalb von HAVING übrigens auch einen Spaltenalias aus der SELECT-Liste verwenden.