PIVOT и UNPIVOT в SQL: шпаргалка
Карьерник — квиз-тренажёр в Telegram с 1500+ вопросами для собесов аналитика. SQL, Python, A/B, метрики. Бесплатно.
Что такое PIVOT
Поворот таблицы: строки → колонки. Из длинного формата (long) в широкий (wide).
Пример. Есть продажи по месяцам:
month category revenue
2026-01 sql 100
2026-01 python 80
2026-02 sql 120
2026-02 python 90Хотим получить:
month sql python
2026-01 100 80
2026-02 120 90Это и есть PIVOT.
PIVOT в разных СУБД
| СУБД | Нативный PIVOT | Через CASE | Функция |
|---|---|---|---|
| PostgreSQL | нет | да | crosstab (extension tablefunc) |
| MSSQL | да (оператор PIVOT) | да | — |
| MySQL | нет | да | — |
| Oracle | да (11g+) | да | — |
| BigQuery | да (PIVOT в стандарте) | да | — |
| Snowflake | да | да | — |
| ClickHouse | нет | да | — |
Универсальный способ — через CASE + GROUP BY. Работает везде.
1. PIVOT через CASE (универсально)
SELECT
month,
SUM(CASE WHEN category = 'sql' THEN revenue ELSE 0 END) AS sql_rev,
SUM(CASE WHEN category = 'python' THEN revenue ELSE 0 END) AS python_rev
FROM sales
GROUP BY month;2. PIVOT в MSSQL / Azure SQL
SELECT month, [sql], [python]
FROM (
SELECT month, category, revenue FROM sales
) src
PIVOT (
SUM(revenue)
FOR category IN ([sql], [python])
) pvt;3. PIVOT в BigQuery / Snowflake
SELECT *
FROM sales
PIVOT (
SUM(revenue) FOR category IN ('sql', 'python')
);4. PIVOT в Postgres через crosstab
-- нужен extension:
-- CREATE EXTENSION tablefunc;
SELECT *
FROM crosstab(
'SELECT month, category, revenue FROM sales ORDER BY 1, 2',
'SELECT DISTINCT category FROM sales ORDER BY 1'
) AS ct(month DATE, sql NUMERIC, python NUMERIC);5. Pivot с несколькими агрегатами
SELECT
month,
SUM(CASE WHEN category = 'sql' THEN revenue END) AS sql_rev,
SUM(CASE WHEN category = 'python' THEN revenue END) AS python_rev,
COUNT(CASE WHEN category = 'sql' THEN 1 END) AS sql_orders,
COUNT(CASE WHEN category = 'python' THEN 1 END) AS python_orders
FROM sales
GROUP BY month;UNPIVOT — обратная операция
Поворот колонок в строки. Из wide в long.
Было:
month sql python
2026-01 100 80Хотим:
month category revenue
2026-01 sql 100
2026-01 python 806. UNPIVOT через UNION ALL (универсально)
SELECT month, 'sql' AS category, sql_rev AS revenue FROM sales_wide
UNION ALL
SELECT month, 'python' AS category, python_rev FROM sales_wide;7. UNPIVOT в MSSQL
SELECT month, category, revenue
FROM sales_wide
UNPIVOT (
revenue FOR category IN (sql_rev, python_rev)
) upvt;8. UNPIVOT в Postgres через VALUES / LATERAL
SELECT
s.month,
v.category,
v.revenue
FROM sales_wide s
CROSS JOIN LATERAL (
VALUES
('sql', s.sql_rev),
('python', s.python_rev)
) AS v(category, revenue);9. UNPIVOT в BigQuery / Snowflake
SELECT * FROM sales_wide
UNPIVOT (
revenue FOR category IN (sql_rev, python_rev)
);Когда использовать PIVOT
- Отчёт в «таблично-сводном» формате для бизнеса
- Упрощение дашборда (отчёт в Tableau / PowerBI легче строить из wide)
- Подготовка данных для ML (один признак — одна колонка)
Когда использовать UNPIVOT
- Нормализация данных после импорта из Excel
- Визуализация в long-формате (ggplot, seaborn, Tableau)
- Универсальная обработка временных рядов
Частые ошибки
Ошибка 1. Хардкод всех значений
-- плохо: если добавится новая категория, запрос сломается
SUM(CASE WHEN category = 'sql' THEN revenue END)
SUM(CASE WHEN category = 'python' THEN revenue END)
-- лучше: динамический SQL (но он сложнее)В production для «неизвестного числа колонок» используют dynamic SQL.
Ошибка 2. COUNT с CASE
-- COUNT считает и NULL, если нет ELSE → неверно
COUNT(CASE WHEN cond THEN 1 ELSE 0 END) -- считает ВСЕ строки
COUNT(CASE WHEN cond THEN 1 END) -- считает только где условие истинноОшибка 3. Забыть GROUP BY при PIVOT через CASE
-- без GROUP BY вернётся одна строка с агрегатами по всей таблице
SELECT SUM(CASE WHEN ... THEN ... END) FROM sales;
-- правильно: группировка по ключу
SELECT month, SUM(CASE ...) FROM sales GROUP BY month;Ошибка 4. UNPIVOT с колонками разных типов
-- ошибка: string и number в одной колонке
UNPIVOT (value FOR col IN (name_str, age_int))
-- требует CAST к общему типуПроизводительность
- PIVOT через CASE — такой же по скорости, как обычный GROUP BY
- Нативный PIVOT (MSSQL, Snowflake) — оптимизированнее, но разница небольшая
- crosstab в Postgres — быстрее CASE на широких таблицах
Связанные темы
FAQ
Как сделать PIVOT в Postgres?
Через CASE + GROUP BY (универсально) или через crosstab в extension tablefunc.
В MySQL есть PIVOT?
Нет нативного. Только через CASE + GROUP BY.
Что быстрее: PIVOT через CASE или нативный PIVOT?
Обычно сопоставимо. Используйте тот, что удобнее для чтения.
Можно ли PIVOT с динамическим набором колонок?
Только через dynamic SQL (формируете запрос как строку, потом EXECUTE). Громоздко, но работает.
Тренируйте SQL — откройте тренажёр с 1500+ вопросами для собесов аналитиков.