Как сделать PIVOT в SQL

Карьерник — квиз-тренажёр в Telegram с 1500+ вопросами для собесов аналитика. SQL, Python, A/B, метрики. Бесплатно.

Зачем это знать

Менеджер просит: «сделай отчёт по продажам — строки месяцы, столбцы продукты». В SQL данные лежат long-format (один столбец — продукт, один — сумма), а надо wide. Это и есть PIVOT.

На собесах часто дают задачу «разверните таблицу». Без PIVOT — много повторений. С native PIVOT — красиво, но не во всех БД. Универсальный способ — CASE WHEN + GROUP BY.

Исходные данные

| month   | product | sales |
|---------|---------|-------|
| 2026-01 | A       | 100   |
| 2026-01 | B       | 200   |
| 2026-02 | A       | 150   |
| 2026-02 | B       | 300   |

Нужно:

| month   | A    | B    |
|---------|------|------|
| 2026-01 | 100  | 200  |
| 2026-02 | 150  | 300  |

Способ 1: CASE WHEN (универсальный)

SELECT
    month,
    SUM(CASE WHEN product = 'A' THEN sales END) AS a,
    SUM(CASE WHEN product = 'B' THEN sales END) AS b
FROM sales
GROUP BY month;

Работает во всех СУБД. Минус: column list захардкожен.

Способ 2: PIVOT (MSSQL, Oracle)

SELECT * FROM sales
PIVOT (
    SUM(sales) FOR product IN ([A], [B])
) AS pvt;

Чище, но ограничен по диалектам.

Способ 3: BigQuery PIVOT

SELECT * FROM sales
PIVOT (SUM(sales) FOR product IN ('A', 'B'));

Динамический PIVOT

Если список колонок неизвестен — нужно dynamic SQL:

-- MSSQL
DECLARE @cols NVARCHAR(MAX);
SELECT @cols = STRING_AGG(QUOTENAME(product), ',')
FROM (SELECT DISTINCT product FROM sales) p;

DECLARE @sql NVARCHAR(MAX) = '
SELECT * FROM sales
PIVOT (SUM(sales) FOR product IN (' + @cols + ')) AS pvt';

EXEC sp_executesql @sql;

На практике — обычно проще сделать это в Python/BI-tool.

UNPIVOT (обратная операция)

Из wide в long:

-- MSSQL
SELECT month, product, sales
FROM wide_sales
UNPIVOT (sales FOR product IN (A, B)) AS u;

В Postgres — через UNION ALL или UNNEST.

PIVOT в ClickHouse

Через arrayJoin или groupArray:

SELECT month,
       sumIf(sales, product = 'A') AS a,
       sumIf(sales, product = 'B') AS b
FROM sales
GROUP BY month;

sumIf элегантнее CASE WHEN.

На собесе

«Разверни таблицу sales так, чтобы строки — месяцы, столбцы — продукты».

Идеальный ответ: CASE WHEN + GROUP BY. Если спросят «а если динамический список» — упомянуть dynamic SQL или BI-tool.

Частые ошибки

SUM vs MAX

Используйте SUM если может быть несколько значений. MAX — если ровно одно значение на ключ (иначе потеряете данные).

Missing values = NULL

CASE WHEN ... THEN x без ELSE даёт NULL для не-матчей. SUM(NULL) ok.

Слишком много колонок

Если результат 100 колонок — pivot не нужен. Оставьте long format + BI-tool.

Связанные темы

FAQ

Native PIVOT в Postgres?

Нет. Используйте CASE WHEN или crosstab() в tablefunc extension.

PIVOT замедляет?

Нет. Это просто GROUP BY с conditional aggregation.

Когда не нужен PIVOT?

Если можно сделать в BI (Tableau, Metabase) — лучше там. SQL pivot — для ETL.


Тренируйте SQL — откройте тренажёр с 1500+ вопросами для собесов.