Klassisches Reporting-Problem: du brauchst Summen pro Kategorie und Gesamtsummen und Sub-Summen pro Sub-Kategorie — alles in einem Resultat. Ohne GROUPING SETS würdest du das mit mehreren UNION ALL-Queries bauen. Mit GROUPING SETS, ROLLUP oder CUBE geht's in einer einzigen Query, in einem Pass über die Daten — und ist deutlich lesbarer.

Beispiel-Daten

SQL orders mit Kategorien
CREATE TABLE orders (
    id          bigserial PRIMARY KEY,
    country     text NOT NULL,
    category    text NOT NULL,
    total       numeric(10, 2) NOT NULL
);

INSERT INTO orders (country, category, total) VALUES
    ('DE', 'Books',    99.95),
    ('DE', 'Books',    49.95),
    ('DE', 'Software',199.95),
    ('AT', 'Books',    29.95),
    ('AT', 'Software', 79.95),
    ('CH', 'Books',    19.95);

GROUPING SETS — explizite Aggregations-Mengen

SQL Pro Land + Pro Kategorie + Gesamt
SELECT
    country,
    category,
    sum(total) AS revenue
FROM orders
GROUP BY GROUPING SETS (
    (country),            -- Aggregat pro Land
    (category),           -- Aggregat pro Kategorie
    ()                    -- Gesamtsumme
)
ORDER BY country NULLS LAST, category NULLS LAST;

Output:

SQL
 country | category |  revenue
---------+----------+----------
 AT      | NULL     |  109.90
 CH      | NULL     |   19.95
 DE      | NULL     |  349.85
 NULL    | Books    |  199.80
 NULL    | Software |  279.90
 NULL    | NULL     |  479.70

Drei verschiedene Aggregations-Ebenen in einer Query:

  • (country) — Summe pro Land (3 Zeilen, category ist NULL)
  • (category) — Summe pro Kategorie (2 Zeilen, country ist NULL)
  • () — die leere Klammer = Gesamtsumme (1 Zeile, beide NULL)

Das NULL in den Spalten signalisiert: „diese Spalte war kein Gruppierungs-Kriterium für diese Zeile".

ROLLUP — Hierarchische Zwischensummen

ROLLUP ist eine Kurzschreibweise für hierarchische Aggregation: jede Ebene plus alle übergeordneten:

SQL ROLLUP über Land + Kategorie
SELECT country, category, sum(total) AS revenue
FROM orders
GROUP BY ROLLUP(country, category)
ORDER BY country NULLS LAST, category NULLS LAST;

Output:

SQL
 country | category |  revenue
---------+----------+----------
 AT      | Books    |   29.95
 AT      | Software |   79.95
 AT      | NULL     |  109.90    <- Zwischensumme AT
 CH      | Books    |   19.95
 CH      | NULL     |   19.95    <- Zwischensumme CH
 DE      | Books    |  149.90
 DE      | Software |  199.95
 DE      | NULL     |  349.85    <- Zwischensumme DE
 NULL    | NULL     |  479.70    <- Gesamtsumme

ROLLUP(a, b) ist äquivalent zu GROUPING SETS ((a, b), (a), ()). Der Name kommt von „auf-rollen": von Detail (Land + Kategorie) → Zwischensumme (Land) → Gesamt.

Klassisch in Finanz-Reports: pro Quartal/Monat/Tag mit Zwischensummen pro Quartal/Monat und Gesamtsumme.

CUBE — Alle Kombinationen

CUBE(a, b) macht die Kreuztabelle: jede mögliche Teilmenge der Spalten:

SQL
SELECT country, category, sum(total) AS revenue
FROM orders
GROUP BY CUBE(country, category)
ORDER BY country NULLS LAST, category NULLS LAST;

Output:

SQL
 country | category |  revenue
---------+----------+----------
 AT      | Books    |   29.95
 AT      | Software |   79.95
 AT      | NULL     |  109.90
 CH      | Books    |   19.95
 CH      | NULL     |   19.95
 DE      | Books    |  149.90
 DE      | Software |  199.95
 DE      | NULL     |  349.85
 NULL    | Books    |  199.80    <- Gesamt pro Kategorie
 NULL    | Software |  279.90
 NULL    | NULL     |  479.70

CUBE(a, b) = GROUPING SETS ((a, b), (a), (b), ()). Jede mögliche Kombination — nützlich für Pivot-Reports, in denen man von jeder Seite zugreifen können will.

Bei N Spalten produziert CUBE 2^N Aggregations-Mengen. Bei 5 Spalten sind das 32 — schnell viel. Sparsam einsetzen.

GROUPING()-Funktion: NULL unterscheiden

Problem: in den obigen Outputs sind die NULL-Werte gemischt — manchmal bedeutet NULL „diese Spalte war Aggregations-Ebene", manchmal echtes NULL aus der Tabelle. Wie unterscheiden?

SQL
SELECT
    country,
    category,
    GROUPING(country)  AS country_grouped,
    GROUPING(category) AS category_grouped,
    sum(total)         AS revenue
FROM orders
GROUP BY ROLLUP(country, category)
ORDER BY country_grouped, country, category_grouped, category;

GROUPING(col) liefert 1, wenn die Spalte in dieser Zeile aggregiert wurde (also „NULL bedeutet hier 'alles aggregiert'"); 0, wenn die Spalte konkret war.

Praktisch zum Markieren von Zwischensummen-Zeilen im UI:

SQL
SELECT
    CASE
        WHEN GROUPING(country) = 1 THEN 'TOTAL'
        ELSE country
    END AS country_label,
    CASE
        WHEN GROUPING(category) = 1 THEN 'subtotal'
        ELSE category
    END AS category_label,
    sum(total) AS revenue
FROM orders
GROUP BY ROLLUP(country, category)
ORDER BY country_label, category_label;

Damit kannst du deine Reports so formatieren, dass die Zwischensummen-Zeilen klar erkennbar sind.

Wann was?

PatternUse-Case
GROUP BY a, bklassische Aggregation, eine Ebene
GROUP BY GROUPING SETS (...)mehrere konkrete Aggregations-Mengen — flexibel
GROUP BY ROLLUP(a, b)hierarchische Reports mit Zwischensummen
GROUP BY CUBE(a, b)Pivot/Kreuztabelle, alle Kombinationen

Faustregel: für „Total am Ende" → ROLLUP. Für „Detail + bestimmte Aggregate" → GROUPING SETS (am flexibelsten). Für „Pivot in alle Richtungen" → CUBE.

Besonderheiten

Eine Query, ein Pass — performance-mäßig ein Gewinn.

Ohne GROUPING SETS müsstest du mehrere Queries mit UNION ALL schreiben — jede liest die Tabelle separat. Mit GROUPING SETS macht Postgres alles in einem Scan. Bei großen Tabellen merklich schneller, vor allem mit Aggregat-Plänen wie HashAggregate.

NULL-Bedeutung wird zweideutig.

In den Resultaten kann NULL „Aggregations-Ebene" oder „echtes NULL aus Daten" bedeuten. Bei Tabellen, die echte NULLs haben, ist das verwirrend. Lösung: GROUPING(col) als zusätzliche Spalte oder COALESCE(col, '<all>') im SELECT, um die zwei Fälle zu unterscheiden.

CUBE wird schnell groß.

Für N Spalten produziert CUBE 2^N Aggregations-Mengen. Bei 6 Spalten = 64 — und entsprechend viele Resultat-Zeilen. Im Reporting-Workflow okay, im Frontend selten direkt zeigbar. Lieber gezielt GROUPING SETS mit den 4-5 wirklich gebrauchten Kombinationen.

Verschachtelte Klauseln möglich.

GROUPING SETS ((country, category), ROLLUP(country)) mischt verschiedene Konstrukte. Mächtig, aber selten nötig — meistens reicht eine der drei Formen pur. In komplexen BI-Reports kann's helfen.

ORDER BY mit NULLS LAST ist meist gewollt.

Aggregations-Zeilen haben NULL in den nicht-gruppierten Spalten. Standard-ORDER BY setzt NULLs ans Ende bei ASC. Wer Zwischensummen UNTER den Detail-Zeilen will, schreibt ORDER BY country NULLS LAST explizit — sonst landen sie je nach DB-Default oben oder unten.

Cross-DB-Kompatibilität: SQL-Standard.

GROUPING SETS, ROLLUP, CUBE sind alle SQL:1999/2003-Standard. Funktionieren auf Postgres, Oracle, SQL Server, DB2 — nicht aber auf MySQL (das hat nur WITH ROLLUP-Suffix als eingeschränkte Variante). Bei plattformübergreifenden Code-Bases prüfen.

Weiterführende Ressourcen

Externe Quellen

/ Weiter

Zurück zu Aggregation & Window Functions

Zur Übersicht