PostgreSQL конвертировать столбцы в строки? Транспонирование?
У меня есть функция PostgreSQL (или таблица), которая дает мне следующий вывод:
Sl.no username Designation salary etc..
1 A XYZ 10000 ...
2 B RTS 50000 ...
3 C QWE 20000 ...
4 D HGD 34343 ...
Теперь я хочу вывод, как показано ниже:
Sl.no 1 2 3 4 ...
Username A B C D ...
Designation XYZ RTS QWE HGD ...
Salary 10000 50000 20000 34343 ...
Как это сделать?
4 ответа
Опираясь на мой ответ на столе формы:
CREATE TABLE tbl (
sl_no int
, username text
, designation text
, salary int
);
Каждая строка приводит к возвращению нового столбца. С таким динамическим типом возврата вряд ли возможно сделать его полностью динамическим с помощью одного обращения к базе данных. Демонстрация решений в два этапа:
- Создать запрос
- Выполнить сгенерированный запрос
Как правило, это ограничено максимальным количеством столбцов, которое может содержать таблица. Так что не вариант для таблиц с более чем 1600 строк (или меньше). Подробности:
Postgres 9.3 или старше
Динамическое решение с crosstab()
- Полностью динамический, работает для любого стола. Укажите имя таблицы в двух местах:
SELECT 'SELECT *
FROM crosstab(
''SELECT unnest(''' || quote_literal(array_agg(attname))
|| '''::text[]) AS col
, row_number() OVER ()
, unnest(ARRAY[' || string_agg(quote_ident(attname)
|| '::text', ',') || ']) AS val
FROM ' || attrelid::regclass || '
ORDER BY generate_series(1,' || count(*) || '), 2''
) t (col text, '
|| (SELECT string_agg('r'|| rn ||' text', ',')
FROM (SELECT row_number() OVER () AS rn FROM tbl) t)
|| ')' AS sql
FROM pg_attribute
WHERE attrelid = 'tbl'::regclass
AND attnum > 0
AND NOT attisdropped
GROUP BY attrelid;
Может быть заключен в функцию с одним параметром...
Создает запрос в форме:
SELECT *
FROM crosstab(
'SELECT unnest(''{sl_no,username,designation,salary}''::text[]) AS col
, row_number() OVER ()
, unnest(ARRAY[sl_no::text,username::text,designation::text,salary::text]) AS val
FROM tbl
ORDER BY generate_series(1,4), 2'
) t (col text, r1 text,r2 text,r3 text,r4 text)
Дает желаемый результат:
col r1 r2 r3 r4
-----------------------------------
sl_no 1 2 3 4
username A B C D
designation XYZ RTS QWE HGD
salary 10000 50000 20000 34343
Простое решение с unnest()
SELECT 'SELECT unnest(''{sl_no, username, designation, salary}''::text[] AS col)
, ' || string_agg('unnest('
|| quote_literal(ARRAY[sl_no::text, username::text, designation::text, salary::text])
|| '::text[]) AS row' || sl_no, E'\n , ') AS sql
FROM tbl;
- Медленно для таблиц с более чем несколькими столбцами.
Создает запрос в форме:
SELECT unnest('{sl_no, username, designation, salary}'::text[]) AS col
, unnest('{10,Joe,Music,1234}'::text[]) AS row1
, unnest('{11,Bob,Movie,2345}'::text[]) AS row2
, unnest('{12,Dave,Theatre,2356}'::text[]) AS row3
, unnest('{4,D,HGD,34343}'::text[]) AS row4
Тот же результат.
Postgres 9.4+
Динамическое решение с crosstab()
Используйте это, если можете. Бьет остальное.
SELECT 'SELECT *
FROM crosstab(
$ct$SELECT u.attnum, t.rn, u.val
FROM (SELECT row_number() OVER () AS rn, * FROM '
|| attrelid::regclass || ') t
, unnest(ARRAY[' || string_agg(quote_ident(attname)
|| '::text', ',') || '])
WITH ORDINALITY u(val, attnum)
ORDER BY 1, 2$ct$
) t (attnum bigint, '
|| (SELECT string_agg('r'|| rn ||' text', ', ')
FROM (SELECT row_number() OVER () AS rn FROM tbl) t)
|| ')' AS sql
FROM pg_attribute
WHERE attrelid = 'tbl'::regclass
AND attnum > 0
AND NOT attisdropped
GROUP BY attrelid;
Работать с attnum
вместо фактических имен столбцов. Проще и быстрее. Присоедините результат к pg_attribute
еще раз или интегрируйте имена столбцов, как в примере с pg 9.3.
Создает запрос в форме:
SELECT *
FROM crosstab(
$ct$SELECT u.attnum, t.rn, u.val
FROM (SELECT row_number() OVER () AS rn, * FROM tbl) t
, unnest(ARRAY[sl_no::text,username::text,designation::text,salary::text])
WITH ORDINALITY u(val, attnum)
ORDER BY 1, 2$ct$
) t (attnum bigint, r1 text, r2 text, r3 text, r4 text);
Это использует целый ряд расширенных функций. Просто слишком много, чтобы объяснить.
Простое решение с unnest()
Один unnest()
Теперь можно использовать несколько массивов для параллельного размещения.
SELECT 'SELECT * FROM unnest(
''{sl_no, username, designation, salary}''::text[]
, ' || string_agg(quote_literal(ARRAY[sl_no::text, username::text, designation::text, salary::text])
|| '::text[]', E'\n, ')
|| E') \n AS t(col,' || string_agg('row' || sl_no, ',') || ')' AS sql
FROM tbl;
Результат:
SELECT * FROM unnest(
'{sl_no, username, designation, salary}'::text[]
,'{10,Joe,Music,1234}'::text[]
,'{11,Bob,Movie,2345}'::text[]
,'{12,Dave,Theatre,2356}'::text[])
AS t(col,row1,row2,row3,row4)
SQL Fiddle работает на стр 9.3.
SELECT
unnest(array['Sl.no', 'username', 'Designation','salary']) AS "Columns",
unnest(array[Sl.no, username, value3Count,salary]) AS "Values"
FROM view_name
ORDER BY "Columns"
Ссылка: convertingColumnsToRows
Если (как и я) вам потребовалась эта информация из скрипта bash, обратите внимание, что для psql есть простой параметр командной строки, чтобы он выводил столбцы таблицы в виде строк:
psql mydbname -x -A -F= -c "SELECT * FROM foo WHERE id=123"
-x
Параметр является ключом к получению psql для вывода столбцов в виде строк.
У меня есть более простой подход, чем указал Эрвин выше, этот работник для меня с Postgres (и я думаю, что он должен работать со всеми основными реляционными базами данных, поддерживающими стандарт SQL)
Вы можете использовать просто UNION вместо кросс-таблицы:
SELECT text 'a' AS "text" UNION SELECT 'b';
text
------
a
b
(2 rows)
Конечно, это зависит от случая, в котором вы собираетесь применить это. Учитывая, что вы заранее знаете, какие поля вам нужны, вы можете использовать этот подход даже для запросов к различным таблицам. То есть:
SELECT 'My first metric' as name, count(*) as total from first_table UNION
SELECT 'My second metric' as name, count(*) as total from second_table
name | Total
------------------|--------
My first metric | 10
My second metric | 20
(2 rows)
Это более приемлемый подход, ИМХО. Посмотрите эту страницу для получения дополнительной информации: https://www.postgresql.org/docs/current/typeconv-union-case.html
Не существует подходящего способа сделать это на обычном SQL или PL/pgSQL.
Будет лучше сделать это в приложении, которое получает данные из БД.