📊 GROUP BY Extensions
ROLLUP, CUBE, GROUPING SETS, GROUPING(), GROUPING_ID()
- →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
| Klauzula | Liczba grupowań | Co generuje |
|---|---|---|
GROUP BY a, b, c | 1 | Tylko (a, b, c) |
ROLLUP(a, b, c) | n+1 = 4 | (a,b,c), (a,b), (a), () |
CUBE(a, b, c) | 2^n = 8 | Wszystkie 8 kombinacji podzbiorów |
GROUPING SETS(...) | Dokładnie tyle ile zdefiniujesz | Wybrane grupowania |
ROLLUP — hierarchiczne sumy cząstkowe
Generuje sumy cząstkowe od najbardziej szczegółowych do ogółu. Kolejność kolumn ma znaczenie!
-- 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;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!| DEPT_ID | JOB_ID | SUM(SALARY) | GROUPING(dept) | GROUPING(job) | GROUPING_ID |
|---|---|---|---|---|---|
| 60 | IT_PROG | 28800 | 0 | 0 | 0 |
| 80 | SA_REP | 250000 | 0 | 0 | 0 |
| 80 | SA_MAN | 61000 | 0 | 0 | 0 |
| 60 | NULL | 28800 | 0 | 1 | 1 |
| 80 | NULL | 311000 | 0 | 1 | 1 |
| NULL | NULL | 339800 | 1 | 1 | 3 |
CUBE — wszystkie kombinacje
Dla n kolumn generuje 2^n grupowań — wszystkie możliwe podzbiory. Idealne do raportów wielowymiarowych.
-- 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);| DEPT | JOB | SUMA |
|---|---|---|
| 60 | IT_PROG | 28800 |
| 80 | SA_REP | 250000 |
| 80 | SA_MAN | 61000 |
| 60 | NULL | 28800 |
| 80 | NULL | 311000 |
| NULL | NULL | 339800 |
| DEPT | JOB | SUMA |
|---|---|---|
| 60 | IT_PROG | 28800 |
| 80 | SA_REP | 250000 |
| 80 | SA_MAN | 61000 |
| 60 | NULL | 28800 |
| 80 | NULL | 311000 |
| NULL | IT_PROG | 28800 |
| NULL | SA_REP | 250000 |
| NULL | SA_MAN | 61000 |
| NULL | NULL | 339800 |
GROUPING SETS — precyzyjny wybór grupowań
Definiujesz dokładnie które grupowania chcesz — bez zbędnych kombinacji. Każda para () to jedno grupowanie.
-- 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śnieGROUPING() — 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.
-- 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".
-- 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) = 1CASE i DECODE — wyrażenia warunkowe
-- 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')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
Ile grupowań (wierszy wynikowych typów) wygeneruje: GROUP BY ROLLUP(a, b, c)?
Ile grupowań wygeneruje: GROUP BY CUBE(a, b)?
GROUPING(department_id) zwraca 1 w wierszu wynikowym, gdy...
Jaką wartość zwróci GROUPING_ID(a, b) dla wiersza grand total (oba NULL z ROLLUP)?
Które grupowania generuje: GROUP BY GROUPING SETS((a,b), (a), ())?
Które stwierdzenie o GROUP_ID() jest POPRAWNE?
W którym miejscu NIE można użyć funkcji GROUPING()?
Co zwróci: GROUP BY GROUPING SETS((a, b), (b, a))?
SELECT department_id, job_id, SUM(salary)
FROM hr.employees
GROUP BY ROLLUP(department_id, job_id)
HAVING GROUPING(department_id) = 1;SELECT department_id, SUM(salary)
FROM hr.employees
WHERE GROUPING(department_id) = 1
GROUP BY ROLLUP(department_id, job_id);SELECT department_id, SUM(salary)
FROM hr.employees
GROUP BY CUBE(department_id)
HAVING GROUP_ID() > 0;SELECT GROUPING(salary), department_id, SUM(salary)
FROM hr.employees
GROUP BY department_id;Ć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.