sql >> Databáze >  >> RDS >> PostgreSQL

PostgreSQL převést sloupce na řádky? Přemístit?

Moje odpověď je založena na tabulce formuláře:

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

Každý řádek má za následek nový sloupec, který se má vrátit. S dynamickým návratovým typem, jako je tento, je stěží možné, aby to bylo zcela dynamické pomocí jediného volání databáze. Demonstrace řešení pomocí dvou kroků :

  1. Vygenerovat dotaz
  2. Provést vygenerovaný dotaz

Obecně je to omezeno maximálním počtem sloupců, které může tabulka pojmout. Není to tedy volba pro tabulky s více než 1600 řádky (nebo méně). Podrobnosti:

  • Jaký je maximální počet sloupců ve výběrovém dotazu PostgreSQL

Postgres 9.3 nebo starší

Dynamické řešení s crosstab()

  • Zcela dynamická, funguje pro jakoukoli tabulku. Zadejte název tabulky ve dvou místa:
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;

Lze zabalit do funkce s jedním parametrem ...
Generuje dotaz ve tvaru:

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)

Vytváří požadovaný výsledek:

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

Jednoduché řešení s 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;
  • Pomalé pro tabulky s více než několika sloupci.

Vygeneruje dotaz ve tvaru:

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

Stejný výsledek.

Postgres 9.4+

Dynamické řešení s crosstab()

Pokud můžete, použijte toto. Předčí ostatní.

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;

Operace s attnum místo skutečných názvů sloupců. Jednodušší a rychlejší. Připojte výsledek k pg_attribute ještě jednou nebo integrujte názvy sloupců jako v příkladu na str. 9.3.
Vygeneruje dotaz ve tvaru:

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);

To využívá celou řadu pokročilých funkcí. Příliš mnoho na vysvětlování.

Jednoduché řešení s unnest()

Jeden unnest() nyní může odpojit několik polí paralelně.

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;

Výsledek:

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 běží na str. 9.3.



  1. Jak povolit rozšíření pro oci8 (Oracle) v php.ini - PHP Upozornění:Spuštění PHP:v neznámém na řádku 0

  2. Jak se vyhnout použití + v čísle verze s SQLiteAssetHelper

  3. Převeďte hex v textové reprezentaci na desítkové číslo

  4. Proč si Oracle 10g nestěžuje na nejednoznačnost sloupců?