Was eine Aggregatfunktion wirklich macht
Die meisten SQL-Funktionen, die dir bisher begegnet sind, arbeiten Zeile für Zeile: UPPER(name) läuft einmal pro Zeile, ROUND(price, 2) läuft einmal pro Zeile. SQLite-Aggregatfunktionen ticken anders. Sie betrachten eine ganze Menge von Zeilen und verdichten sie zu einem einzigen Wert.
Leg dir eine kleine Tabelle zum Experimentieren an:
Fünf Zeilen rein, eine Zeile raus. Genau das ist das mentale Modell hinter SQLite-Aggregatfunktionen: Sie quetschen mehrere Zeilen zu einer Zusammenfassung. Ohne GROUP BY bezieht sich diese Zusammenfassung auf alle Zeilen im Ergebnis.
COUNT: Zeilen zählen oder Werte zählen?
COUNT gibt es in drei Varianten – und der Unterschied ist wichtig:
COUNT(*)zählt Zeilen. NULLs werden mitgezählt. Liefert immer eine Zahl zurück.COUNT(column)zählt nur die Werte in der Spalte, die nicht NULL sind.COUNT(DISTINCT column)zählt die eindeutigen Werte ohne NULL.
Fünf Zeilen, davon drei mit einem amount und drei unterschiedliche Kunden. Wenn dir mal auffällt, dass COUNT(amount) kleiner ist als COUNT(*), dann liegt es genau daran — NULLs zählen nicht mit.
SUM, AVG, MIN, MAX
Die rechnenden Aggregatfunktionen verhalten sich genauso, wie man es erwartet — mit einer stillen Regel im Hintergrund: NULL wird grundsätzlich übersprungen:
AVG ergibt hier (10 + 20 + 30) / 3 = 20.0, nicht 60 / 4 = 15.0. Im Nenner steht nämlich nur die Anzahl der Werte, die nicht NULL sind. Wenn du das anders haben willst — also fehlende Werte lieber als Null behandeln — musst du das explizit angeben:
MIN und MAX funktionieren übrigens auch mit Text und Datumswerten – bei Text wird lexikografisch verglichen, bei Datumsangaben im Standardformat als ISO-String.
SUM oder TOTAL – was ist der Unterschied?
SQLite bringt mit TOTAL eine zweite Aggregatfunktion mit, die wie SUM funktioniert, aber zwei typische Stolperfallen von SUM umgeht:
SUMüber null Zeilen liefertNULL.TOTALgibt0.0zurück.SUMüber ausschließlichNULL-Werte liefertNULL.TOTALgibt0.0zurück.TOTALliefert immer einen Fließkommawert und kann daher nie einen Integer-Überlauf verursachen.
Der Haken: TOTAL ist nicht Standard-SQL, und das stets als REAL gelieferte Ergebnis kann dich überraschen, wenn du eigentlich einen Integer erwartet hast. Greif zu TOTAL, wenn "keine Zeilen heißt null" für deine Anwendung die richtige Antwort ist – und bleib bei SUM, wenn du dich an das SQL-Standardverhalten halten willst.
DISTINCT in Aggregatfunktionen
DISTINCT lässt sich in jeder Aggregatfunktion einsetzen, nicht nur in COUNT. Doppelte Werte werden dabei vor der Aggregation entfernt:
SUM(amount) summiert den Wert jeder einzelnen Zeile. SUM(DISTINCT amount) zählt jeden eindeutigen Betrag nur einmal mit – praktisch zum Beispiel für "Summe der eindeutigen Rechnungsbeträge", aber in der Praxis selten das, was man wirklich braucht. Der Klassiker ist eher COUNT(DISTINCT customer).
FILTER: Aggregation auf einer Teilmenge
Wenn du nur einen Teil der Zeilen aggregieren willst, greifst du intuitiv erstmal zu WHERE. Das Problem: WHERE filtert die komplette Abfrage – du kannst damit nicht gleichzeitig "bezahlte Bestellungen zählen" und "Rückerstattungen zählen". Genau hier kommt FILTER ins Spiel:
Jede FILTER (WHERE ...)-Klausel wirkt nur auf genau diese eine Aggregatfunktion. Ein einziger Durchlauf über die Tabelle, dafür mehrere Teilmengen ausgewertet. Bevor es FILTER gab, hat man dasselbe mit SUM(CASE WHEN status = 'paid' THEN amount END) geschrieben – gleiche Idee, nur mehr Tipparbeit.
GROUP_CONCAT: Strings zusammenfügen
GROUP_CONCAT fällt etwas aus dem Rahmen. Statt einer Zahl gibt diese Funktion die Werte als einen einzigen zusammengesetzten String zurück:
Das Standardtrennzeichen ist ein Komma. Wenn du ein anderes brauchst, übergib es einfach als zweites Argument. Die Reihenfolge ist übrigens nicht garantiert – außer du schreibst GROUP_CONCAT(tag ORDER BY tag). Das lohnt sich besonders, wenn die Ausgabe später im UI landet und stabil bleiben soll.
Aggregatfunktionen ohne GROUP BY verwenden
In allen bisherigen Beispielen ohne GROUP BY kam genau eine Zeile heraus. Genau so ist es vorgesehen: Ein SELECT mit Aggregatfunktionen und ohne GROUP BY liefert eine einzige Zusammenfassungszeile über die gesamte Tabelle (nach Anwendung von WHERE).
Mehrere Aggregatfunktionen lassen sich dabei beliebig kombinieren:
Was du dagegen nicht machen kannst: nicht-aggregierte Spalten mit Aggregaten mischen und sinnvolle Ergebnisse erwarten:
-- Von SQLite erlaubt, aber der Wert von `customer` ist beliebig.
SELECT customer, SUM(amount) FROM orders;
SQLite meckert hier nicht (andere Datenbanken schon), zeigt dann aber irgendeinen zufälligen Kundennamen neben der Summe an. Wenn du eine Summe pro Kunde willst, brauchst du GROUP BY – und genau darum geht's auf der nächsten Seite.
Weiter geht's mit GROUP BY und HAVING
Aggregatfunktionen über die gesamte Tabelle beantworten die Frage „Wie viel insgesamt?". Aggregate pro Gruppe – pro Kunde, pro Monat, pro Status – beantworten die spannenderen Fragen. Mit GROUP BY teilst du die Zeilen vor dem Aggregieren in Gruppen auf, und mit HAVING filterst du auf dem aggregierten Ergebnis. Das schauen wir uns als Nächstes an.
Häufig gestellte Fragen
Was sind Aggregatfunktionen in SQLite?
Aggregatfunktionen nehmen viele Zeilen entgegen und liefern einen einzigen zusammengefassten Wert zurück. Eingebaut sind COUNT, SUM, AVG, MIN, MAX, TOTAL und GROUP_CONCAT. Ohne GROUP BY quetschen sie das komplette Ergebnis in eine einzige Zeile.
Was ist der Unterschied zwischen SUM und TOTAL in SQLite?
Beide addieren Zahlen, aber SUM gibt NULL zurück, wenn alle Eingaben NULL sind, und rechnet nach Möglichkeit mit Integer-Arithmetik (was zu Overflows führen kann). TOTAL liefert immer eine Fließkommazahl und gibt 0.0 zurück, wenn keine Zeilen vorhanden sind. Nimm TOTAL, wenn du auf jeden Fall ein numerisches Ergebnis willst, und SUM, wenn dir das SQL-Standardverhalten wichtig ist.
Wie zähle ich eindeutige Werte in SQLite?
Pack DISTINCT direkt in den Aufruf: COUNT(DISTINCT customer_id). Das zählt eindeutige Werte ohne NULL. Ein einfaches COUNT(column) zählt alle Nicht-NULL-Werte inklusive Duplikaten, und COUNT(*) zählt jede Zeile – egal, ob NULL drin steht oder nicht.
Ignorieren SQLite-Aggregatfunktionen NULL-Werte?
Ja – jede Aggregatfunktion außer COUNT(*) überspringt NULL-Eingaben. AVG teilt durch die Anzahl der Nicht-NULL-Werte, nicht durch die Gesamtzeilenzahl. COUNT(*) ist die Ausnahme: Es zählt Zeilen statt Werte und nimmt NULLs deshalb mit.