KPI-Reports mit CASE WHEN und FILTER in einer Abfrage
Conditional Aggregation kombiniert CASE WHEN oder die FILTER-Klausel mit Aggregatfunktionen und berechnet dadurch mehrere bedingte Kennzahlen in einem einzigen Durchlauf über die Tabelle, statt für jede Kennzahl eine eigene Abfrage zu schreiben. Dieser Artikel zeigt, wie Conditional Aggregation Dashboards und Reports schneller, wartbarer und übersichtlicher macht, und welche Fallstricke bei NULL-Werten und Datentypen typischerweise auftreten.
Inhaltsverzeichnis
- 1. Was Conditional Aggregation löst und warum sie wichtig ist
- 2. Das Grundmuster: CASE WHEN innerhalb von Aggregatfunktionen
- 3. Die FILTER-Klausel als moderne Alternative
- 4. Mehrere Kennzahlen nebeneinander in einer einzigen Zeile
- 5. NULL-Fallstricke bei Conditional Aggregation vermeiden
- 6. Conditional Aggregation kombiniert mit GROUP BY
- 7. Prozentanteile und Quoten aus bedingten Zählungen berechnen
- 8. Performance: ein Scan statt mehrerer Unterabfragen
- 9. CASE WHEN, FILTER und separate Subqueries im Vergleich
- 10. Zusammenfassung
- 11. FAQ
1. Was Conditional Aggregation löst und warum sie wichtig ist
Conditional Aggregation bezeichnet die Technik, eine Bedingung direkt in eine Aggregatfunktion einzubauen, statt die Tabelle mehrfach zu filtern und die Ergebnisse anschließend im Anwendungscode zusammenzuführen. Der klassische Anwendungsfall ist ein Dashboard, das für eine Bestellliste gleichzeitig die Anzahl offener, versendeter und stornierter Bestellungen anzeigen soll. Ohne Conditional Aggregation müsste man drei separate Abfragen schreiben, jede mit einer eigenen WHERE-Bedingung, und die Ergebnisse anschließend manuell zusammenführen.
Mit Conditional Aggregation entsteht stattdessen eine einzige Abfrage, die alle drei Kennzahlen als eigene Spalten in derselben Ergebniszeile zurückgibt. Der Datenbankserver liest die Tabelle dafür nur einmal, statt sie dreimal zu scannen, was gerade bei großen Faktentabellen einen spürbaren Performance-Unterschied macht. Conditional Aggregation ist damit nicht nur eine Frage der Lesbarkeit, sondern eine direkte Optimierungstechnik für Reporting-Abfragen mit mehreren Kennzahlen.
Dieser Artikel zeigt beide gängigen Varianten von Conditional Aggregation, das klassische CASE-WHEN-Muster und die modernere FILTER-Klausel, erklärt typische NULL-Fallstricke und zeigt, wie sich Prozentanteile und Quoten direkt aus bedingt gezählten Werten berechnen lassen, ohne zusätzliche Abfragen oder Anwendungslogik.
2. Das Grundmuster: CASE WHEN innerhalb von Aggregatfunktionen
Das Grundmuster der Conditional Aggregation mit CASE WHEN funktioniert, indem ein CASE-Ausdruck innerhalb der Aggregatfunktion entscheidet, welcher Wert pro Zeile in die Berechnung eingeht. Für eine Zählung lautet das Muster COUNT(CASE WHEN bedingung THEN 1 END), wobei END ohne ELSE automatisch NULL für alle Zeilen liefert, die die Bedingung nicht erfüllen, und COUNT NULL-Werte bekanntlich ignoriert. Für eine Summe lautet das entsprechende Muster SUM(CASE WHEN bedingung THEN betrag ELSE 0 END), wobei hier ein explizites ELSE 0 nötig ist, damit SUM eine Zahl statt NULL zurückgibt, sobald keine Zeile die Bedingung erfüllt.
Der Unterschied zwischen COUNT(CASE WHEN...) und SUM(CASE WHEN...) bei Conditional Aggregation liegt also im ELSE-Zweig: Bei COUNT genügt THEN 1 ohne ELSE, weil NULL ignoriert wird. Bei SUM sollte man ELSE 0 immer explizit angeben, sonst kann eine Gruppe ohne passende Zeilen NULL statt 0 liefern, was nachgelagerte Berechnungen wie Prozentwerte oder weitere Summen verfälscht. Diese kleine, aber wichtige Unterscheidung ist einer der häufigsten Anfängerfehler bei Conditional Aggregation.
-- Conditional aggregation with CASE WHEN: multiple KPIs in one pass
SELECT
DATE_TRUNC('month', order_date) AS month,
COUNT(CASE WHEN status = 'open' THEN 1 END) AS open_orders,
COUNT(CASE WHEN status = 'shipped' THEN 1 END) AS shipped_orders,
COUNT(CASE WHEN status = 'cancelled' THEN 1 END) AS cancelled_orders,
SUM(CASE WHEN status = 'shipped' THEN total_amount ELSE 0 END) AS shipped_revenue
FROM orders
GROUP BY DATE_TRUNC('month', order_date)
ORDER BY month;
3. Die FILTER-Klausel als moderne Alternative
Der SQL-Standard definiert seit SQL:2003 die FILTER-Klausel als saubere Alternative zu CASE WHEN bei Conditional Aggregation. Die Syntax lautet AGGREGATFUNKTION(ausdruck) FILTER (WHERE bedingung), wobei die Bedingung ausschließlich steuert, welche Zeilen in die jeweilige Aggregatfunktion einfließen, ohne den eigentlichen Ausdruck durch einen CASE-Block zu verschachteln. PostgreSQL unterstützt FILTER seit Version 9.4 vollständig, während MySQL und ältere SQL-Server-Versionen die Klausel bislang nicht kennen und weiterhin auf CASE WHEN angewiesen sind.
Der Vorteil von FILTER gegenüber CASE WHEN bei Conditional Aggregation liegt in der Klarheit: Die Bedingung steht direkt neben der Aggregatfunktion, statt in einem verschachtelten Ausdruck versteckt zu sein, und der Aggregatfunktion selbst muss kein künstlicher ELSE-Zweig für NULL oder 0 mitgegeben werden. Für COUNT(*) FILTER (WHERE status = 'shipped') etwa entfällt die sonst nötige CASE-Konstruktion komplett, was den Code deutlich lesbarer macht, besonders wenn viele Bedingungen in einer Abfrage kombiniert werden.
-- Conditional aggregation with the FILTER clause (PostgreSQL, SQL:2003 standard)
SELECT
DATE_TRUNC('month', order_date) AS month,
COUNT(*) FILTER (WHERE status = 'open') AS open_orders,
COUNT(*) FILTER (WHERE status = 'shipped') AS shipped_orders,
COUNT(*) FILTER (WHERE status = 'cancelled') AS cancelled_orders,
SUM(total_amount) FILTER (WHERE status = 'shipped') AS shipped_revenue
FROM orders
GROUP BY DATE_TRUNC('month', order_date)
ORDER BY month;
4. Mehrere Kennzahlen nebeneinander in einer einzigen Zeile
Der eigentliche Mehrwert von Conditional Aggregation zeigt sich, sobald ein Report nicht nur drei, sondern zehn oder mehr Kennzahlen aus derselben Faktentabelle berechnen soll. Ein typisches Beispiel ist ein Vertriebsreport, der pro Region gleichzeitig Umsatz, Anzahl Bestellungen, Anzahl Neukunden, durchschnittlichen Bestellwert und Retourenquote in einer einzigen Ergebniszeile ausweisen soll. Jede dieser Kennzahlen bekommt eine eigene, unabhängige Bedingung, alle nebeneinander in derselben SELECT-Liste.
Ohne Conditional Aggregation müsste man diese zehn Kennzahlen entweder über zehn separate Abfragen mit anschließendem JOIN auf einen gemeinsamen Schlüssel zusammenführen, oder über zehn Subqueries in derselben Abfrage, was jeweils zu zehn unabhängigen Scans der Faktentabelle führt. Conditional Aggregation reduziert das auf genau einen Scan, unabhängig davon, wie viele Kennzahlen in der SELECT-Liste stehen, solange sie auf derselben Gruppierungsebene berechnet werden können.
-- Ten KPIs from one fact table scan using conditional aggregation
SELECT
region,
COUNT(*) AS total_orders,
COUNT(*) FILTER (WHERE is_new_customer) AS new_customer_orders,
SUM(total_amount) AS total_revenue,
ROUND(AVG(total_amount), 2) AS avg_order_value,
COUNT(*) FILTER (WHERE returned) AS returned_orders,
ROUND(
100.0 * COUNT(*) FILTER (WHERE returned) / NULLIF(COUNT(*), 0),
2
) AS return_rate_pct
FROM orders
GROUP BY region
ORDER BY total_revenue DESC;
5. NULL-Fallstricke bei Conditional Aggregation vermeiden
Ein häufiger Fehler bei Conditional Aggregation mit CASE WHEN entsteht, wenn die geprüfte Spalte selbst NULL enthalten kann. Ein Ausdruck wie CASE WHEN status = 'shipped' THEN 1 END liefert bei status IS NULL weder wahr noch falsch, sondern unbekannt, wodurch die betroffene Zeile weder in den THEN- noch in einen ungewollten ELSE-Zweig fällt, sondern schlicht NULL wird und von COUNT ignoriert wird. Das ist in den meisten Fällen erwünscht, muss aber bewusst geprüft werden, wenn NULL-Status-Werte in der Fachlichkeit eine eigene Bedeutung haben.
Ein zweiter Fallstrick betrifft SUM ohne ELSE: SUM(CASE WHEN bedingung THEN betrag END) liefert NULL statt 0, sobald keine Zeile der Gruppe die Bedingung erfüllt, was in nachgelagerten Berechnungen wie Prozentanteilen zu unerwarteten NULL-Werten führen kann. Bei Conditional Aggregation mit der FILTER-Klausel tritt dasselbe Problem auf, weil COUNT(*) FILTER und SUM(spalte) FILTER dem normalen NULL-Verhalten der jeweiligen Aggregatfunktion folgen. Ein COALESCE um das Gesamtergebnis herum schafft in beiden Varianten Abhilfe, wenn eine Zahl statt NULL fachlich erwartet wird.
-- NULL pitfalls in conditional aggregation, and how to guard against them
SELECT
customer_id,
-- Without ELSE: NULL when no row matches, not 0
SUM(CASE WHEN category = 'electronics' THEN amount END) AS electronics_raw,
-- With ELSE 0: always a number, safe for further math
SUM(CASE WHEN category = 'electronics' THEN amount ELSE 0 END) AS electronics_safe,
-- FILTER follows the same NULL behavior as the underlying aggregate
COALESCE(SUM(amount) FILTER (WHERE category = 'electronics'), 0) AS electronics_filter_safe
FROM purchases
GROUP BY customer_id;
6. Conditional Aggregation kombiniert mit GROUP BY
Conditional Aggregation entfaltet ihre volle Wirkung erst im Zusammenspiel mit GROUP BY, weil dadurch bedingte Kennzahlen nicht nur für die gesamte Tabelle, sondern für jede Gruppe einzeln berechnet werden. Ein Report, der pro Kunde die Anzahl bezahlter, offener und stornierter Bestellungen ausweisen soll, kombiniert GROUP BY customer_id mit mehreren COUNT(CASE WHEN...)-Ausdrücken, jeweils mit einer eigenen Statusbedingung. Jede Gruppe erhält dabei ihre eigenen, unabhängig berechneten Werte für jede der bedingten Kennzahlen.
Wichtig ist, dass die Bedingung innerhalb der Aggregatfunktion nicht mit einer WHERE-Klausel auf Zeilenebene verwechselt wird. Eine WHERE-Bedingung würde Zeilen bereits vor der Aggregation komplett aus der Gruppe entfernen und alle anderen Kennzahlen ebenfalls verfälschen. Conditional Aggregation dagegen behält alle Zeilen der Gruppe für die Gesamtzählung, filtert aber gezielt innerhalb jeder einzelnen Aggregatfunktion, was der entscheidende konzeptionelle Unterschied zwischen einer WHERE-Bedingung und Conditional Aggregation via CASE WHEN oder FILTER ist.
-- Conditional aggregation per customer group, WHERE would remove other rows entirely
SELECT
customer_id,
COUNT(CASE WHEN status = 'paid' THEN 1 END) AS paid_orders,
COUNT(CASE WHEN status = 'open' THEN 1 END) AS open_orders,
COUNT(CASE WHEN status = 'cancelled' THEN 1 END) AS cancelled_orders,
COUNT(*) AS total_orders
FROM orders
GROUP BY customer_id
ORDER BY total_orders DESC;
7. Prozentanteile und Quoten aus bedingten Zählungen berechnen
Ein besonders praktischer Anwendungsfall für Conditional Aggregation ist die direkte Berechnung von Prozentanteilen innerhalb derselben Abfrage, ohne dass Rohzahlen erst ins Reporting-Tool exportiert und dort weiterverarbeitet werden müssen. Die Formel lautet typischerweise 100.0 mal die bedingte Zählung geteilt durch die Gesamtzählung, wobei NULLIF die Gesamtzählung vor einer Division durch null schützt. Diese Kombination aus Conditional Aggregation und Division ist die Grundlage nahezu jeder Konversions- oder Fehlerquote in einem SQL-Report.
Bei Datenbanksystemen, die Ganzzahldivision durchführen, wie es in älteren MySQL-Konfigurationen vorkommen kann, muss mindestens ein Operand der Division explizit als Dezimalzahl behandelt werden, meist durch die 100.0 statt 100 als Multiplikator. Ohne diesen expliziten Dezimalpunkt liefert die Division bei Conditional Aggregation in solchen Fällen gerundete Ganzzahlen wie 0 oder 1 statt eines aussagekräftigen Prozentwerts, was in der Praxis zu falsch interpretierten Dashboards führt.
8. Performance: ein Scan statt mehrerer Unterabfragen
Der Performance-Vorteil von Conditional Aggregation gegenüber mehreren separaten Abfragen mit anschließendem JOIN liegt darin, dass die Datenbank die relevante Tabelle oder den relevanten Index nur einmal lesen muss, statt einmal pro Kennzahl. Bei einer Faktentabelle mit mehreren Millionen Zeilen kann der Unterschied zwischen einem einzigen Table-Scan und zehn separaten Scans mit anschließendem JOIN mehrere Sekunden Laufzeitunterschied bedeuten, besonders wenn kein passender Index existiert und jede Abfrage einen Full Table Scan auslöst.
Ein weiterer Performance-Aspekt betrifft den Query-Planer: Bei Conditional Aggregation sieht der Planer eine einzige Abfrage mit einer klaren GROUP-BY-Struktur und kann diese in der Regel effizient mit einem Hash- oder Sort-basierten Aggregations-Plan ausführen. Bei mehreren separaten Abfragen mit nachträglichem JOIN muss der Planer zusätzlich die Join-Strategie zwischen den Zwischenergebnissen optimieren, was zusätzlichen Planungsaufwand und potenziell zusätzliche Sortierschritte bedeutet, die bei Conditional Aggregation von vornherein entfallen.
9. CASE WHEN, FILTER und separate Subqueries im Vergleich
Die folgende Übersicht vergleicht die drei gängigen Ansätze, um mehrere bedingte Kennzahlen aus derselben Tabelle zu berechnen, und zeigt, wann welcher Ansatz für Conditional Aggregation sinnvoll ist.
| Ansatz | Table Scans | Datenbank-Support | Lesbarkeit |
|---|---|---|---|
| CASE WHEN in Aggregatfunktion | 1 | Alle SQL-Datenbanken | Gut, aber verschachtelt bei vielen Bedingungen |
| FILTER-Klausel | 1 | PostgreSQL, nicht MySQL/SQL Server | Sehr klar, Bedingung direkt sichtbar |
| Separate Subqueries + JOIN | N (eine pro Kennzahl) | Alle SQL-Datenbanken | Aufwendig bei vielen Kennzahlen |
| Anwendungscode nachträglich | N, plus Transfer aller Rohdaten | Unabhängig von der Datenbank | Logik verteilt über zwei Schichten |
In der Praxis ist CASE WHEN die portabelste Variante von Conditional Aggregation, während die FILTER-Klausel dort, wo sie verfügbar ist, die klarere und oft auch geringfügig performantere Wahl ist, weil der Query-Planer die Filterbedingung direkt erkennt, statt sie aus einem CASE-Ausdruck ableiten zu müssen. Separate Subqueries oder eine Verarbeitung im Anwendungscode sollten für mehrere Kennzahlen aus derselben Tabelle die letzte Wahl sein.
Mironsoft
SQL-Reporting, KPI-Dashboards und Query-Optimierung
Zehn Kennzahlen, zehn Abfragen? Das geht auch in einer.
Wir bauen Reporting-Abfragen mit Conditional Aggregation, die mehrere Kennzahlen in einem einzigen Table-Scan berechnen, statt Dashboards mit dutzenden separaten Abfragen zu überladen.
Query-Refactoring
Mehrere Abfragen zu einer einzigen Conditional-Aggregation-Abfrage zusammenführen
Dashboard-Performance
Reduzierung der Table-Scans für schnellere, konsistentere Dashboards
Datenbank-Beratung
Auswahl zwischen CASE WHEN und FILTER passend zu eurer Datenbankplattform
Wer Conditional Aggregation konsequent einsetzt, ersetzt eine wachsende Zahl separater Abfragen durch wenige, gut strukturierte Reporting-Queries, die sich einfacher warten, testen und dokumentieren lassen als ein Geflecht aus Subqueries und nachgelagerter Anwendungslogik.
10. Zusammenfassung
Conditional Aggregation kombiniert Bedingungen mit Aggregatfunktionen, um mehrere Kennzahlen in einem einzigen Durchlauf über die Tabelle zu berechnen. Das klassische Muster nutzt CASE WHEN innerhalb von COUNT oder SUM, wobei bei SUM ein explizites ELSE 0 nötig ist, um NULL statt 0 zu vermeiden. Die FILTER-Klausel ist die modernere, klarere Alternative, allerdings nicht in allen Datenbanksystemen verfügbar.
Der größte Nutzen von Conditional Aggregation liegt in der Reduktion der Table Scans: Statt einer Abfrage pro Kennzahl entsteht eine einzige Abfrage mit beliebig vielen bedingten Spalten. NULLIF schützt dabei zuverlässig vor Division durch null bei Prozentberechnungen, und die richtige Kombination mit GROUP BY liefert bedingte Kennzahlen pro Gruppe statt nur für die Gesamttabelle.
Conditional Aggregation: das Wichtigste auf einen Blick
CASE WHEN Grundmuster
COUNT(CASE WHEN bedingung THEN 1 END) für Zählungen, SUM(CASE WHEN bedingung THEN betrag ELSE 0 END) für Summen.
FILTER-Klausel
AGGREGATFUNKTION(ausdruck) FILTER (WHERE bedingung), klarer als CASE WHEN, aber nicht überall verfügbar.
NULL beachten
Ohne ELSE 0 liefert SUM bei fehlenden Treffern NULL, nicht 0. COALESCE schafft Abhilfe.
Performance
Ein Table-Scan für beliebig viele Kennzahlen, statt einer Abfrage pro Kennzahl.