PostgreSQL
 sql >> база данни >  >> RDS >> PostgreSQL

PostgreSQL конвертира колони в редове? Транспониране?

Базирайки отговора си на таблица от формата:

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

Всеки ред води до нова колона за връщане. С динамичен тип връщане като този, едва ли е възможно това да се направи напълно динамично с едно извикване към базата данни. Демонстриране на решения сдве стъпки :

  1. Генериране на заявка
  2. Изпълнете генерирана заявка

Обикновено това е ограничено от максималния брой колони, които може да съдържа таблица. Така че не е опция за таблици с повече от 1600 реда (или по-малко). Подробности:

  • Какъв е максималният брой колони в заявка за избор на PostgreSQL

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 още веднъж или интегрирайте имената на колони като в примера на стр. 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.



  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Най-добрите инструменти за сигнали и известия за PostgreSQL

  2. Вземете броя на записите, засегнати от INSERT или UPDATE в PostgreSQL

  3. Производителност на TPC-H след PostgreSQL 8.3

  4. Как да се свържа с PostgreSQL, без да посоча име на база данни?

  5. Как да накарам моята база данни postgresql да използва съпоставяне без значение за главни букви?