📊 GROUP BY Extensions

ROLLUP, CUBE, GROUPING SETS, GROUPING(), GROUPING_ID()

⚡ Kluczowe fakty do zapamiętania
  • ROLLUP(a, b) = automatyczne sumy hierarchiczne. Wyobraź sobie raport sprzedaży: dostajesz wiersz dla każdej kombinacji (dział+stanowisko), potem sumę dla każdego działu osobno, potem grand total. Dla n kolumn → n+1 grupowań. Kolejność kolumn ma znaczenie: ROLLUP(a,b) ≠ ROLLUP(b,a).
  • CUBE(a, b) = ROLLUP na sterydach — generuje WSZYSTKIE możliwe kombinacje grupowań. Dla (dział, rok) dostaniesz: (dział+rok), (tylko dział), (tylko rok) i grand total. 2 kolumny = 2² = 4 grupowania. Używany w hurtowniach danych (OLAP).
  • GROUPING SETS((a,b),(a),()) = Twój własny wybór grupowań. Zamiast całego ROLLUP/CUBE, mówisz Oracleowi dokładnie jakich grupowań chcesz. Efektywnie zastępuje kilka zapytań z UNION ALL.
  • GROUPING(kolumna) = detektor 'magicznych NULLi'. Zwraca 1 gdy NULL pochodzi z ROLLUP/CUBE (wiersz sumaryczny), 0 gdy to prawdziwy rekord. Bez GROUPING() nie możesz odróżnić NULL-sumy od NULL-braku wartości.
  • GROUPING_ID(a, b) = GROUPING(a) i GROUPING(b) skodowane bitowo w jedną liczbę. Wiersz szczegółowy=0, suma tylko b=1, suma tylko a=2, grand total=3. Łatwiejsze filtrowanie niż kilka GROUPING().
  • GROUP_ID() = 0 dla pierwszego wystąpienia danego grupowania, 1 dla duplikatu. Używaj HAVING GROUP_ID() = 0 gdy GROUPING SETS powtarza to samo grupowanie — inaczej dostaniesz zduplikowane wiersze.
  • NULL w wyniku ROLLUP/CUBE oznacza 'wszystkie wartości tej kolumny' (agregacja) — NIE brak danych. To jest 'magiczny NULL'. Użyj GROUPING() żeby to odróżnić od prawdziwego NULL.

Porównanie rozszerzeń GROUP BY

KlauzulaLiczba grupowańCo generuje
GROUP BY a, b, c1Tylko (a, b, c)
ROLLUP(a, b, c)n+1 = 4(a,b,c), (a,b), (a), ()
CUBE(a, b, c)2^n = 8Wszystkie 8 kombinacji podzbiorów
GROUPING SETS(...)Dokładnie tyle ile zdefiniujeszWybrane grupowania

ROLLUP — hierarchiczne sumy cząstkowe

Generuje sumy cząstkowe od najbardziej szczegółowych do ogółu. Kolejność kolumn ma znaczenie!

sql
-- ROLLUP: (department_id, job_id) → (department_id) → ()
SELECT department_id, job_id, SUM(salary) AS suma
FROM hr.employees
GROUP BY ROLLUP(department_id, job_id);

-- Ekwiwalent ręczny (ROLLUP zastępuje 3 UNION ALL):
SELECT department_id, job_id, SUM(salary) FROM hr.employees GROUP BY department_id, job_id
UNION ALL
SELECT department_id, NULL, SUM(salary) FROM hr.employees GROUP BY department_id
UNION ALL
SELECT NULL, NULL, SUM(salary) FROM hr.employees;
⚠️
Kolejność w ROLLUP ma znaczenie
ROLLUP(a, b) daje: (a,b), (a), () — sumy na poziomie a.
ROLLUP(b, a) daje: (b,a), (b), () — sumy na poziomie b. To są INNE wyniki!
Wynik ROLLUP(department_id, job_id) — przykład (uproszczone dane)
DEPT_IDJOB_IDSUM(SALARY)GROUPING(dept)GROUPING(job)GROUPING_ID
60IT_PROG28800000
80SA_REP250000000
80SA_MAN61000000
60NULL28800011
80NULL311000011
NULLNULL339800113
Żółte wiersze = sumy cząstkowe wygenerowane przez ROLLUP. NULL w JOB_ID (wiersze 4-5) = suma dla całego działu. NULL w obu (wiersz 6) = grand total. GROUPING()=1 oznacza że to magiczny NULL z ROLLUP, nie brak danych!

CUBE — wszystkie kombinacje

Dla n kolumn generuje 2^n grupowań — wszystkie możliwe podzbiory. Idealne do raportów wielowymiarowych.

sql
-- CUBE: (department_id, job_id) → generuje 4 grupowania (2^2)
-- (dept, job), (dept), (job), ()
SELECT department_id, job_id, SUM(salary) AS suma
FROM hr.employees
GROUP BY CUBE(department_id, job_id);
⚖️ Porównanie wynikówROLLUP(a,b) vs CUBE(a,b) — różnica w liczbie wierszy
ROLLUP(dept_id, job_id) — 3 typy grupowań
DEPTJOBSUMA
60IT_PROG28800
80SA_REP250000
80SA_MAN61000
60NULL28800
80NULL311000
NULLNULL339800
CUBE(dept_id, job_id) — 4 typy grupowań
DEPTJOBSUMA
60IT_PROG28800
80SA_REP250000
80SA_MAN61000
60NULL28800
80NULL311000
NULLIT_PROG28800
NULLSA_REP250000
NULLSA_MAN61000
NULLNULL339800
💡 ROLLUP generuje 3 typy: (dept+job), (dept), (). CUBE dodaje jeszcze (job) — sumy po samym stanowisku bez podziału na dział (wiersze 6-8 po prawej). Wyróżnione wiersze = sumy z NULL.

GROUPING SETS — precyzyjny wybór grupowań

Definiujesz dokładnie które grupowania chcesz — bez zbędnych kombinacji. Każda para () to jedno grupowanie.

sql
-- Tylko te 3 grupowania: (dept,job), (dept), ()
SELECT department_id, job_id, SUM(salary)
FROM hr.employees
GROUP BY GROUPING SETS(
    (department_id, job_id),
    (department_id),
    ()
);

-- WAŻNE: GROUPING SETS((a),(b)) ≠ GROUPING SETS((a,b))
-- (a),(b) = dwa osobne grupowania (po jednej kolumnie)
-- (a,b)   = jedno grupowanie po dwóch kolumnach jednocześnie

GROUPING() — identyfikacja wierszy sumarycznych

Zwraca 1 gdy kolumna jest NULL wskutek ROLLUP/CUBE (wiersz sumaryczny), 0 gdy to prawdziwa wartość. Bez tego nie możesz odróżnić NULL-z-agregacji od NULL-w-danych.

sql
-- Praktyczne użycie — czytelne etykiety zamiast NULL:
SELECT
    CASE GROUPING(department_id) WHEN 1 THEN 'WSZYSTKIE DZIAŁY' ELSE TO_CHAR(department_id) END AS dept,
    CASE GROUPING(job_id)        WHEN 1 THEN 'WSZYSTKIE STANOWISKA' ELSE job_id END AS job,
    SUM(salary) AS suma
FROM hr.employees
GROUP BY ROLLUP(department_id, job_id);

-- UWAGA: GROUPING() można używać tylko w SELECT, HAVING, ORDER BY
-- NIE można w WHERE — to najczęstszy błąd!

GROUPING_ID() — jeden numer zamiast wielu flag

Koduje bitowo wiele kolumn GROUPING() w jedną liczbę. Łatwiejsze sortowanie i filtrowanie po poziomie agregacji. Wartość odczytujesz jak binarnie: bit=1 znaczy "ta kolumna jest sumaryczna".

sql
-- GROUPING_ID(dept, job):
-- Wiersz (dept, job) → oba=0  → 0  (00 binarnie)
-- Wiersz (dept)      → job=1  → 1  (01 binarnie)
-- Wiersz ()          → oba=1  → 3  (11 binarnie) = grand total

SELECT department_id, job_id, SUM(salary),
       GROUPING_ID(department_id, job_id) AS gid
FROM hr.employees
GROUP BY ROLLUP(department_id, job_id)
ORDER BY GROUPING_ID(department_id, job_id);

-- Wyświetl TYLKO wiersze sumaryczne na poziomie działu (gid=1):
HAVING GROUPING_ID(department_id, job_id) = 1

CASE i DECODE — wyrażenia warunkowe

sql
-- CASE prosta (porównanie równości):
CASE job_id
    WHEN 'AD_PRES' THEN 'Prezes'
    WHEN 'IT_PROG' THEN 'Programista'
    ELSE 'Inne'
END

-- CASE przeszukiwana (dowolne warunki):
CASE
    WHEN salary > 15000 THEN 'Wysoka'
    WHEN salary > 8000  THEN 'Średnia'
    ELSE 'Niska'
END

-- DECODE (Oracle-specific, krótszy zapis prostego CASE):
DECODE(job_id, 'AD_PRES', 'Prezes', 'IT_PROG', 'Programista', 'Inne')
ℹ️
Kiedy używać czego?
ROLLUP — raporty hierarchiczne (rok → kwartał → miesiąc)
CUBE — raporty cross-wymiarowe (hurtownie, OLAP)
GROUPING SETS — gdy potrzebujesz konkretnych grupowań bez zbędnych kombinacji
GROUPING() — zamień NULL na czytelną etykietę
GROUPING_ID() — filtrowanie/sortowanie po poziomie agregacji

Pytania egzaminacyjne

🔤
Pytanie ABCD

Ile grupowań (wierszy wynikowych typów) wygeneruje: GROUP BY ROLLUP(a, b, c)?

🔤
Pytanie ABCD

Ile grupowań wygeneruje: GROUP BY CUBE(a, b)?

🔤
Pytanie ABCD

GROUPING(department_id) zwraca 1 w wierszu wynikowym, gdy...

🔤
Pytanie ABCD

Jaką wartość zwróci GROUPING_ID(a, b) dla wiersza grand total (oba NULL z ROLLUP)?

🔤
Pytanie ABCD

Które grupowania generuje: GROUP BY GROUPING SETS((a,b), (a), ())?

🔤
Pytanie ABCD

Które stwierdzenie o GROUP_ID() jest POPRAWNE?

🔤
Pytanie ABCD

W którym miejscu NIE można użyć funkcji GROUPING()?

🔤
Pytanie ABCD

Co zwróci: GROUP BY GROUPING SETS((a, b), (b, a))?

🔍 Czy to się wykona?
SELECT department_id, job_id, SUM(salary)
FROM hr.employees
GROUP BY ROLLUP(department_id, job_id)
HAVING GROUPING(department_id) = 1;
🔍 Czy to się wykona?
SELECT department_id, SUM(salary)
FROM hr.employees
WHERE GROUPING(department_id) = 1
GROUP BY ROLLUP(department_id, job_id);
🔍 Czy to się wykona?
SELECT department_id, SUM(salary)
FROM hr.employees
GROUP BY CUBE(department_id)
HAVING GROUP_ID() > 0;
🔍 Czy to się wykona?
SELECT GROUPING(salary), department_id, SUM(salary)
FROM hr.employees
GROUP BY department_id;
🔎 Znajdź błąd — wskaż niepoprawną linię
1SELECT department_id, job_id, SUM(salary)
2FROM hr.employees
3WHERE GROUPING(department_id) = 1
4GROUP BY ROLLUP(department_id, job_id)
5ORDER BY 1;
🔎 Znajdź błąd — wskaż niepoprawną linię
1SELECT department_id,
2 CASE GROUPING(department_id)
3 WHEN 1 THEN 'Grand Total'
4 ELSE TO_CHAR(department_id)
5 END AS dept_label,
6 SUM(salary)
7FROM hr.employees
8GROUP BY department_id;
🔎 Znajdź błąd — wskaż niepoprawną linię
1SELECT department_id, job_id,
2 GROUPING_ID(department_id, job_id) AS gid,
3 SUM(salary)
4FROM hr.employees
5GROUP BY ROLLUP(department_id, job_id)
6HAVING gid = 0;

Ćwiczenia

💪

Ćwiczenie 1 — ROLLUP: suma pensji wg działu i stanowiska

Wyświetl sumę pensji grupując po department_id i job_id z użyciem ROLLUP. Dodaj kolumny GROUPING() dla obu pól, aby odróżnić sumy cząstkowe od danych szczegółowych.

💪

Ćwiczenie 2 — GROUPING SETS z etykietami

Wyświetl sumę pensji dla grupowań: (department_id, job_id) oraz () (total). Zamiast NULL wyświetl czytelne etykiety.

💪

Ćwiczenie 3 — CUBE i GROUPING_ID

Zastosuj CUBE na department_id i job_id. Wyświetl tylko wiersze będące podsumowaniem na poziomie działu (job_id jest sumaryczny, ale department_id nie). Użyj GROUPING_ID do filtrowania.