Собесов

DataInterview SQL — pivot выручки по категориям × месяцам

SQLPivotСредняяJunior

Условие

Таблица sales(product_category, sale_date, revenue). Сформируйте отчёт, где строки — категории, колонки — месяцы текущего года (jan, feb, ..., dec), значения — сумма выручки. Пустые ячейки — 0.

Решение

Условная агрегация (universal)

SELECT
  product_category,
  SUM(CASE WHEN EXTRACT(MONTH FROM sale_date) = 1  THEN revenue ELSE 0 END) AS jan,
  SUM(CASE WHEN EXTRACT(MONTH FROM sale_date) = 2  THEN revenue ELSE 0 END) AS feb,
  SUM(CASE WHEN EXTRACT(MONTH FROM sale_date) = 3  THEN revenue ELSE 0 END) AS mar,
  SUM(CASE WHEN EXTRACT(MONTH FROM sale_date) = 4  THEN revenue ELSE 0 END) AS apr,
  SUM(CASE WHEN EXTRACT(MONTH FROM sale_date) = 5  THEN revenue ELSE 0 END) AS may,
  SUM(CASE WHEN EXTRACT(MONTH FROM sale_date) = 6  THEN revenue ELSE 0 END) AS jun,
  SUM(CASE WHEN EXTRACT(MONTH FROM sale_date) = 7  THEN revenue ELSE 0 END) AS jul,
  SUM(CASE WHEN EXTRACT(MONTH FROM sale_date) = 8  THEN revenue ELSE 0 END) AS aug,
  SUM(CASE WHEN EXTRACT(MONTH FROM sale_date) = 9  THEN revenue ELSE 0 END) AS sep,
  SUM(CASE WHEN EXTRACT(MONTH FROM sale_date) = 10 THEN revenue ELSE 0 END) AS oct,
  SUM(CASE WHEN EXTRACT(MONTH FROM sale_date) = 11 THEN revenue ELSE 0 END) AS nov,
  SUM(CASE WHEN EXTRACT(MONTH FROM sale_date) = 12 THEN revenue ELSE 0 END) AS dec
FROM sales
WHERE EXTRACT(YEAR FROM sale_date) = EXTRACT(YEAR FROM CURRENT_DATE)
GROUP BY product_category
ORDER BY product_category;

PIVOT в SQL Server / Oracle

SELECT * FROM (
  SELECT product_category, EXTRACT(MONTH FROM sale_date) AS m, revenue
  FROM sales
  WHERE EXTRACT(YEAR FROM sale_date) = 2025
) src
PIVOT (
  SUM(revenue)
  FOR m IN (1 AS jan, 2 AS feb, ..., 12 AS dec)
);

Postgres/MySQL не имеют синтаксиса PIVOT — только условная агрегация (или tablefunc.crosstab в Postgres).

Postgres crosstab

SELECT *
FROM crosstab(
  $$ SELECT product_category, EXTRACT(MONTH FROM sale_date)::INT, SUM(revenue)
     FROM sales WHERE EXTRACT(YEAR FROM sale_date) = 2025
     GROUP BY 1, 2 ORDER BY 1, 2 $$,
  $$ SELECT generate_series(1, 12) $$
) AS ct(product_category TEXT, jan NUMERIC, feb NUMERIC, ..., dec NUMERIC);

tablefunc нужно подключить расширением.

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

  1. Месяцы без продаж. Условная агрегация с ELSE 0 гарантирует нули. Без ELSE 0 будет NULL → COALESCE.
  2. dec ключевое слово. В Postgres зарезервировано как часть DECIMAL. Чаще ок, но в кавычках безопаснее: "dec".
  3. Кросс-year смешение. Без WHERE year = ... категории за 2023 и 2024 склеятся в одну строку — частая ошибка.
  4. Хардкод 12 месяцев. Не масштабируется на «по неделям»/«по дням». Для динамического pivot — приложение или dynamic SQL.

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

12 SUM(CASE WHEN month = N THEN revenue ELSE 0 END) — простой и работает везде. Pivot-синтаксис только в SQL Server / Oracle / Postgres-через-extension.

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

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

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