sql >> Database teknologi >  >> RDS >> PostgreSQL

PostgreSQL konvertere kolonner til rækker? Transponere?

Baserer mit svar på en tabel med formularen:

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

Hver række resulterer i en ny kolonne, der skal returneres. Med en dynamisk returtype som denne er det næppe muligt at gøre dette helt dynamisk med et enkelt kald til databasen. Demonstrer løsninger med to trin :

  1. Generer forespørgsel
  2. Udfør genereret forespørgsel

Generelt er dette begrænset af det maksimale antal kolonner, en tabel kan indeholde. Så ikke en mulighed for tabeller med mere end 1600 rækker (eller færre). Detaljer:

  • Hvad er det maksimale antal kolonner i en PostgreSQL-valgsforespørgsel

Postgres 9.3 eller ældre

Dynamisk løsning med crosstab()

  • Fuldstændig dynamisk, fungerer til enhver tabel. Angiv tabelnavnet i to steder:
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;

Kunne pakkes ind i en funktion med en enkelt parameter ...
Genererer en forespørgsel af formen:

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)

Giver det ønskede resultat:

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

Simpel løsning med 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;
  • Langsomt for tabeller med mere end et par kolonner.

Genererer en forespørgsel i formen:

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

Samme resultat.

Postgres 9.4+

Dynamisk løsning med crosstab()

Brug dette, hvis du kan. Slår resten.

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;

Arbejder med attnum i stedet for egentlige kolonnenavne. Enklere og hurtigere. Føj resultatet til pg_attribute endnu en gang eller integrer kolonnenavne som i eksemplet på side 9.3.
Genererer en forespørgsel af formen:

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

Dette bruger en lang række avancerede funktioner. Bare for meget at forklare.

Simpel løsning med unnest()

Én unnest() kan nu tage flere arrays for at unnestere parallelt.

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;

Resultat:

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 kører på side 9.3.



  1. JSON_PRETTY() – Formater JSON-dokumenter for lettere læsbarhed i MySQL

  2. Hvordan rydder man ODP.NET-forbindelsespuljen ved forbindelsesfejl?

  3. MySQL 'opret skema' og 'opret database' - Er der nogen forskel

  4. Indlæser klassen com.mysql.jdbc.Driver ... er forældet besked