sql >> Database >  >> RDS >> PostgreSQL

PostgreSQL kolommen naar rijen converteren? transponeren?

Mijn antwoord baseren op een tabel van het formulier:

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

Elke rij resulteert in een nieuwe kolom om terug te keren. Met een dynamisch retourtype als dit is het nauwelijks mogelijk om dit volledig dynamisch te maken met een enkele aanroep van de database. Oplossingen demonstreren met twee stappen :

  1. Zoekopdracht genereren
  2. Gegenereerde zoekopdracht uitvoeren

Over het algemeen wordt dit beperkt door het maximale aantal kolommen dat een tabel kan bevatten. Dus geen optie voor tabellen met meer dan 1600 rijen (of minder). Details:

  • Wat is het maximum aantal kolommen in een PostgreSQL-selectiequery

Postgres 9.3 of ouder

Dynamische oplossing met crosstab()

  • Volledig dynamisch, werkt voor elke tafel. Geef de tabelnaam op in twee plaatsen:
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;

Kan worden verpakt in een functie met een enkele parameter ...
Genereert een query van de vorm:

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)

Produceert het gewenste resultaat:

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

Eenvoudige oplossing met 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;
  • Traag voor tabellen met meer dan een paar kolommen.

Genereert een query van het formulier:

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

Zelfde resultaat.

Postgres 9.4+

Dynamische oplossing met crosstab()

Gebruik dit als je kunt. Verslaat de rest.

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;

Werkend met attnum in plaats van echte kolomnamen. Eenvoudiger en sneller. Voeg het resultaat toe aan pg_attribute nogmaals of integreer kolomnamen zoals in het voorbeeld van pg 9.3.
Genereert een query van het formulier:

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

Dit maakt gebruik van een hele reeks geavanceerde functies. Gewoon te veel om uit te leggen.

Eenvoudige oplossing met unnest()

Eén unnest() kan nu meerdere arrays parallel gebruiken om te unnesten.

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;

Resultaat:

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 draait op pagina 9.3.



  1. SQL-sleutels, MUL versus PRI versus UNI

  2. 6 manieren om de grootte van een database in SQL Server te controleren met T-SQL

  3. Hoeveel gebruikers hebben toegang tot ondersteuning?

  4. PDO::fetchAll vs. PDO::fetch in a loop