Собесов

DataLearn SQL-101: Pivot, ROLLUP, CUBE — кросс-таблицы в SQL

SQLReportingСредняяMiddle

Условие

Дана таблица sales(dt, region, category, amount). Постройте отчёт:

  1. По строкам — region, по колонкам — category, в ячейках — SUM(amount).
  2. Добавьте итоги по строкам и колонкам (Total row / column).
  3. Покажите промежуточные итоги по region независимо от category (subtotals).

Решение

Подход

Pivot можно сделать через CASE WHEN (универсально), PIVOT (Snowflake/BigQuery/SQL Server) или Postgres crosstab. Итоги — GROUPING SETS, ROLLUP, CUBE.

1) Pivot вручную

SELECT
    region,
    SUM(CASE WHEN category = 'electronics' THEN amount END) AS electronics,
    SUM(CASE WHEN category = 'fashion'     THEN amount END) AS fashion,
    SUM(CASE WHEN category = 'home'        THEN amount END) AS home,
    SUM(CASE WHEN category = 'food'        THEN amount END) AS food,
    SUM(amount)                                              AS total
FROM sales
GROUP BY region
ORDER BY region;

Универсально работает везде. Минус: список категорий нужно знать заранее. Для динамического pivot — dynamic SQL.

1') Snowflake PIVOT

SELECT * FROM (
    SELECT region, category, amount FROM sales
)
PIVOT (SUM(amount) FOR category IN ('electronics', 'fashion', 'home', 'food'))
ORDER BY region;

2) ROLLUP — иерархические итоги

SELECT
    COALESCE(region,   'TOTAL') AS region,
    COALESCE(category, 'ALL')   AS category,
    SUM(amount) AS amount
FROM sales
GROUP BY ROLLUP (region, category)
ORDER BY region, category;

Даёт: (region, category), (region, NULL) — итог по региону, (NULL, NULL) — всех. Это left-prefix, иерархия от слева направо.

3) GROUPING SETS — независимые срезы

SELECT
    region,
    category,
    SUM(amount) AS amount
FROM sales
GROUP BY GROUPING SETS (
    (region, category),
    (region),
    (category),
    ()
)
ORDER BY region NULLS LAST, category NULLS LAST;

Даёт четыре среза: детальный, по region, по category, общий.

4) CUBE — все комбинации

GROUP BY CUBE (region, category)
-- = GROUPING SETS ((region, category), (region), (category), ())

Различение «итоговая строка» и «NULL по факту»

Если в данных есть честный NULL — COALESCE(region, 'TOTAL') не отличит. Используем GROUPING():

SELECT
    CASE WHEN GROUPING(region)   = 1 THEN 'TOTAL' ELSE region END AS region,
    CASE WHEN GROUPING(category) = 1 THEN 'ALL'   ELSE category END AS category,
    SUM(amount) AS amount
FROM sales
GROUP BY ROLLUP (region, category);

GROUPING(col) = 1 означает «эта строка — итог по col».

Подводные камни

  1. Pivot через CASE требует знать список категорий. Если категории динамические — string_agg(... 'WHEN x THEN amount END') через dynamic SQL.
  2. ROLLUP vs CUBE: ROLLUP — иерархический (region → region+category), CUBE — все 2^n сочетания. CUBE дороже.
  3. COALESCE(col, 'TOTAL') ломается, если в данных есть NULL — путаются итоги и реальные NULL.
  4. GROUP BY GROUPING SETS ((a), ()) даёт grand total — частый шаблон.
  5. ORDER BY NULLS FIRST/LAST — для красивого вывода итогов.
  6. PIVOT в Snowflake / SQL Server не одинаков синтаксически: Snowflake требует subquery с только нужными колонками — иначе Snowflake группирует по всем остальным.
  7. Postgres crosstab требует extension tablefunc; на проде доступен не всегда.

Эталонный ответ

-- Pivot + итоги одним запросом
SELECT
    COALESCE(region, 'TOTAL')                                     AS region,
    SUM(CASE WHEN category='electronics' THEN amount END)         AS electronics,
    SUM(CASE WHEN category='fashion'     THEN amount END)         AS fashion,
    SUM(CASE WHEN category='home'        THEN amount END)         AS home,
    SUM(CASE WHEN category='food'        THEN amount END)         AS food,
    SUM(amount)                                                   AS total
FROM sales
GROUP BY ROLLUP (region)
ORDER BY GROUPING(region), region;

ROLLUP / GROUPING SETS дают итоги; pivot реализуется через CASE либо PIVOT (Snowflake/BQ). Для точного различения итогов от NULL — функция GROUPING().

Хочешь увидеть разбор?

Зарегистрируйся бесплатно — откроется развёрнутое решение этой задачи и ещё 4 на выбор.

Зарегистрироваться и увидеть разбор
Уже есть аккаунт? Войти