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   ...

Как это сделать?

Ответ 1

Опираясь на мой ответ на столе формы:

CREATE TABLE tbl (
   sl_no int
 , username text
 , designation text
 , salary int
);

Каждая строка приводит к возвращению нового столбца. С таким динамическим типом возврата вряд ли возможно сделать это полностью динамическим с помощью одного обращения к базе данных. Демонстрация решений в два этапа:

  1. Создать запрос
  2. Выполнить сгенерированный запрос

Как правило, это ограничено максимальным количеством столбцов, которое может содержать таблица. Так что не вариант для таблиц с более чем 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.

Ответ 2

Если (например, мне) вам понадобилась эта информация из bash script, обратите внимание, что для psql имеется простой переключатель командной строки, чтобы он выводил столбцы таблицы в виде строк:

psql mydbname -x -A -F= -c "SELECT * FROM foo WHERE id=123"

Опция -x - это ключ к получению psql для вывода столбцов в виде строк.

Ответ 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"

Ссылка: преобразованиеКолонкиТоки

Ответ 4

Нет правильного способа сделать это в простом SQL или PL/pgSQL.

Это будет лучше сделать это в приложении, которое получает данные из БД.

Ответ 5

У меня есть более простой подход, чем указал Эрвин выше, этот работник для меня с 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

Ответ 6

select
 uuid_generate_v4() as "j"
,"x"."i"
, 1 as "t"
,'territory' as "s"
,"j"."key" as "k"
,"j"."value" as "v"
,true as "a"
from ( select "x".rowguid as "i", row_to_json(x) "j" from adventureworks.salesterritory "x" ) "x" CROSS JOIN LATERAL json_each(x."j") AS "j" -- select * from adventureworks.salesterritory limit 100
where "j"."key" not in ('rowguid', 'modifieddate', 'territoryid')