
Транспонирование таблицы в SQL – это операция, при которой строки превращаются в столбцы, а столбцы – в строки. Такой приём особенно полезен, когда требуется агрегировать данные по категориям, создавать сводные отчёты или подготавливать результат для аналитических систем. В отличие от стандартных выборок, транспонирование требует понимания функций PIVOT и UNPIVOT, а также приёмов динамического формирования SQL-запросов.
В Microsoft SQL Server транспонирование чаще всего реализуется с помощью оператора PIVOT, позволяющего группировать значения по выбранным полям и распределять их по столбцам. Например, если нужно преобразовать список продаж, где каждая строка отражает месяц и сумму, в таблицу, где месяцы станут столбцами, достаточно применить PIVOT SUM() с динамическим определением названий колонок. Однако в других системах, таких как PostgreSQL или MySQL, аналогичного синтаксиса нет, и здесь приходится использовать CASE WHEN или подзапросы для имитации поведения.
Для работы с неизвестным набором значений в столбцах используют динамическое SQL – формирование текста запроса на основе данных из системных таблиц. Такой подход позволяет гибко обрабатывать отчёты, где количество категорий или параметров заранее неизвестно. Грамотно построенный запрос обеспечивает высокую производительность и уменьшает дублирование логики на уровне приложения.
Оптимальная стратегия транспонирования зависит от конкретной СУБД и объёма данных. При больших таблицах стоит учитывать затраты на сортировку и агрегацию, использовать индексы и ограничивать выборку нужными условиями. Правильная организация процесса позволяет не только упростить аналитику, но и повысить эффективность работы с данными без лишних преобразований на стороне клиента.
Когда возникает необходимость транспонирования таблицы в SQL

При интеграции данных из разных источников транспонирование помогает унифицировать формат таблиц. Один источник может хранить данные в «длинном» формате (по одной записи на показатель), другой – в «широком» (каждый показатель в отдельной колонке). Без преобразования таких структур объединение или агрегирование становится невозможным.
Также транспонирование применяется при подготовке данных для BI-инструментов, где столбцы используются как измерения, а строки – как значения. В таких случаях SQL-транспонирование с помощью операторов PIVOT или условной агрегации (CASE WHEN) позволяет создать оптимальный вид данных для построения графиков и дашбордов.
В аналитических системах транспонирование упрощает вычисление динамики по временным срезам. Например, превращение строк с датами и значениями продаж в столбцы по месяцам ускоряет сводные расчеты и снижает нагрузку на клиентскую сторону.
Таким образом, транспонирование используется всякий раз, когда формат хранения данных не совпадает с требуемым форматом для анализа, отчетности или интеграции, и позволяет сделать структуру таблицы максимально пригодной для конкретных SQL-задач.
Использование оператора CASE для поворота строк в столбцы

Оператор CASE позволяет выполнять поворот данных без применения встроенных функций PIVOT. Такой подход подходит, когда требуется гибкое управление условиями и формированием столбцов.
Пример: имеется таблица sales с колонками year, region, amount. Нужно преобразовать строки с регионами в отдельные столбцы.
SELECT
year,
SUM(CASE WHEN region = 'North' THEN amount ELSE 0 END) AS North,
SUM(CASE WHEN region = 'South' THEN amount ELSE 0 END) AS South,
SUM(CASE WHEN region = 'West' THEN amount ELSE 0 END) AS West
FROM sales
GROUP BY year;
Такой запрос группирует данные по годам и суммирует продажи по каждому региону, формируя столбцы динамически через условия CASE.
- Каждое условие
CASEпроверяет значение в колонке и возвращает число, строку или дату, в зависимости от типа данных. - Использование агрегатных функций (
SUM,MAX,COUNT) обязательно при группировке. - Количество создаваемых столбцов фиксировано и задаётся вручную.
Если количество категорий неизвестно заранее, формирование SQL-запроса с CASE можно автоматизировать с помощью динамического SQL, где список уникальных значений формируется через подзапрос или STRING_AGG().
Преимущества подхода:
- Совместим с любыми версиями SQL Server, PostgreSQL, MySQL и Oracle.
- Позволяет применять дополнительные условия (например, фильтрацию по датам) внутри каждого выражения
CASE. - Обеспечивает полный контроль над форматированием и типами данных результирующих столбцов.
Недостаток – необходимость вручную обновлять список столбцов при добавлении новых категорий. Однако при небольшом количестве значений CASE остаётся самым прозрачным и управляемым способом транспонирования данных.
Применение агрегатных функций при транспонировании данных

При преобразовании строк таблицы в столбцы часто требуется объединять несколько значений в одну ячейку. Для этого в SQL используют агрегатные функции, такие как SUM(), MAX(), MIN(), COUNT() и STRING_AGG() (или GROUP_CONCAT() в MySQL). Они позволяют группировать данные по ключевым полям и агрегировать значения в новых столбцах, формируя компактную структуру.
Например, нужно транспонировать таблицу продаж, где каждая строка отражает сумму по конкретному месяцу:
| Год | Месяц | Продажи |
|---|---|---|
| 2024 | Январь | 1200 |
| 2024 | Февраль | 900 |
| 2024 | Март | 1100 |
Требуется вывести один ряд на год с колонками по месяцам. Решение через условную агрегацию:
SELECT
Год,
SUM(CASE WHEN Месяц = 'Январь' THEN Продажи END) AS Январь,
SUM(CASE WHEN Месяц = 'Февраль' THEN Продажи END) AS Февраль,
SUM(CASE WHEN Месяц = 'Март' THEN Продажи END) AS Март
FROM Продажи
GROUP BY Год;
Функция SUM() здесь не только суммирует значения, но и формирует структуру транспонированной таблицы. Аналогично можно применять MAX() или STRING_AGG() при работе с текстовыми данными, чтобы объединить строки в один столбец:
SELECT
Категория,
STRING_AGG(Товар, ', ') AS Список_товаров
FROM Продажи
GROUP BY Категория;
Главное – правильно подобрать агрегатную функцию под тип данных и задачу. Числовые показатели агрегируются через SUM() или AVG(), а текстовые – через STRING_AGG(). Это позволяет не только транспонировать таблицу, но и одновременно сократить объём данных без потери смысла.
Как транспонировать таблицу с динамическими столбцами

Когда количество столбцов неизвестно заранее, стандартные конструкции PIVOT становятся неприменимыми. Для таких случаев используется динамическое SQL-преобразование, формирующее список столбцов в процессе выполнения запроса.
Пример для SQL Server:
DECLARE @cols NVARCHAR(MAX), @query NVARCHAR(MAX);
SELECT @cols = STRING_AGG(QUOTENAME(ColumnName), ',')
FROM (SELECT DISTINCT ColumnName FROM SourceTable) AS x;
SET @query = 'SELECT ' + @cols + '
FROM (
SELECT RowId, ColumnName, Value
FROM SourceTable
) AS src
PIVOT (MAX(Value) FOR ColumnName IN (' + @cols + ')) AS p;';
EXEC sp_executesql @query;
Конструкция STRING_AGG формирует список названий столбцов на основе уникальных значений из исходных данных. Динамический запрос создаёт структуру таблицы в момент выполнения, позволяя адаптироваться к любому количеству полей без ручного редактирования.
В PostgreSQL можно применить crosstab() из модуля tablefunc совместно с генерацией SQL через format() и string_agg(). Пример:
DO $$
DECLARE
cols text;
sql text;
BEGIN
SELECT string_agg(DISTINCT quote_ident(column_name), ', ')
INTO cols FROM source_table;
sql := format('SELECT * FROM crosstab(
''SELECT row_id, column_name, value FROM source_table ORDER BY 1'',
''SELECT DISTINCT column_name FROM source_table ORDER BY 1''
) AS ct(row_id int, %s text);', cols);
EXECUTE sql;
END $$;
При работе с динамическими столбцами важно контролировать типы данных, сортировку и уникальность ключей. Любое дублирование значений в поле, выбранном для транспонирования, приведёт к ошибке или некорректным результатам. Используйте агрегирующие функции (MAX, SUM, STRING_AGG) для устранения неоднозначности.
Использование PIVOT и UNPIVOT в разных СУБД

В Microsoft SQL Server оператор PIVOT преобразует строки в столбцы, группируя данные по заданным полям. Пример: при анализе продаж можно превратить значения месяцев в отдельные столбцы. Основной синтаксис: SELECT ... FROM таблица PIVOT (SUM(значение) FOR столбец_категории IN ([Jan], [Feb], [Mar])) AS p. Обратная операция выполняется через UNPIVOT, который превращает столбцы обратно в строки.
В Oracle используется аналогичный механизм, но с отличиями в синтаксисе. Пример: SELECT * FROM таблица PIVOT (SUM(значение) FOR категория IN ('A' AS col_a, 'B' AS col_b)). Oracle поддерживает вложенные и составные PIVOT-операции, что удобно при сложных аналитических отчетах. Для обратного преобразования применяется UNPIVOT INCLUDE NULLS, если требуется сохранить пустые значения.
PostgreSQL не имеет встроенных операторов PIVOT и UNPIVOT, но аналог можно реализовать с помощью функции crosstab() из расширения tablefunc. Пример: SELECT * FROM crosstab('SELECT категория, месяц, сумма FROM продажи ORDER BY 1,2') AS (категория text, jan numeric, feb numeric, mar numeric). Обратное преобразование выполняется через UNION ALL или LATERAL JOIN с функцией unnest().
В MySQL до версии 8.0.21 прямого аналога PIVOT нет, поэтому используется динамический SQL с конструкцией CASE WHEN и функцией GROUP_CONCAT для генерации столбцов. Пример: SELECT категория, SUM(CASE WHEN месяц='Jan' THEN сумма END) AS Jan .... Для обратного преобразования можно использовать UNION ALL или JSON_TABLE() при работе с JSON-данными.
В SAP HANA оператор PIVOT реализован через SELECT ... PIVOT (SUM(значение) FOR категория IN ('A', 'B', 'C')). Для обратного преобразования применяется UNNEST, что обеспечивает высокую производительность при больших объемах данных.
Выбор между встроенным PIVOT и ручной реализацией зависит от СУБД и объема данных. При частой смене структуры рекомендуется динамический SQL, тогда как фиксированные отчеты целесообразно строить через стандартный PIVOT.
Практический пример транспонирования таблицы с нуля

Исходная таблица sales содержит данные о продажах по кварталам:
| product | q1 | q2 | q3 | q4 | |----------|-----|-----|-----|-----| | phone | 120 | 150 | 180 | 200 | | laptop | 90 | 110 | 130 | 160 |
Задача – преобразовать строки кварталов в отдельные записи, чтобы получить структуру:
| product | quarter | amount | |----------|----------|--------| | phone | q1 | 120 | | phone | q2 | 150 | | ... | ... | ... |
Транспонирование выполняется с помощью UNION ALL без готовых функций pivot/unpivot, чтобы показать процесс вручную:
SELECT product, 'q1' AS quarter, q1 AS amount FROM sales UNION ALL SELECT product, 'q2', q2 FROM sales UNION ALL SELECT product, 'q3', q3 FROM sales UNION ALL SELECT product, 'q4', q4 FROM sales;
Такой подход универсален для систем без поддержки UNPIVOT. Если СУБД поддерживает стандартные средства, то эквивалентный запрос выглядит компактнее:
SELECT product, quarter, amount FROM sales UNPIVOT (amount FOR quarter IN (q1, q2, q3, q4)) AS u;
Для проверки корректности результата удобно использовать агрегатные функции. Например, сумма по каждому товару до и после транспонирования должна совпадать:
SELECT product, SUM(q1 + q2 + q3 + q4) AS total_before FROM sales GROUP BY product; SELECT product, SUM(amount) AS total_after FROM ( SELECT product, 'q1' AS quarter, q1 AS amount FROM sales UNION ALL SELECT product, 'q2', q2 FROM sales UNION ALL SELECT product, 'q3', q3 FROM sales UNION ALL SELECT product, 'q4', q4 FROM sales ) t GROUP BY product;
Такой метод позволяет гибко перестраивать таблицы для анализа, особенно при динамических наборах показателей, где количество столбцов заранее неизвестно.
Вопрос-ответ:
Как можно транспонировать таблицу в SQL, если я не знаю заранее количество строк или столбцов?
Если структура данных неизвестна заранее, можно использовать динамический SQL. Сначала нужно получить список уникальных значений из столбца, который станет заголовками новых колонок, например, через `SELECT DISTINCT`. Затем — собрать SQL-запрос с помощью строковой конкатенации, где каждая новая колонка создаётся через агрегирующую функцию, например `MAX(CASE WHEN …)`. Такой запрос выполняется через `EXEC` или `sp_executesql`. Это универсальный подход, особенно если структура таблицы может меняться.
Чем отличается использование `PIVOT` от ручного подхода через `CASE WHEN`?
Оператор `PIVOT` делает то же самое, что и ручная группировка с `CASE`, но выглядит компактнее и читабельнее. Однако `PIVOT` поддерживается не всеми системами управления базами данных — он есть, например, в SQL Server и Oracle, но отсутствует в MySQL и PostgreSQL. Если нужен переносимый код, лучше использовать `CASE WHEN`. Если вы работаете в SQL Server, `PIVOT` экономит время и делает запрос аккуратнее.
