PostgreSQL
 sql >> Datenbank >  >> RDS >> PostgreSQL

PostgreSQL Spalten in Zeilen konvertieren? Transponieren?

Meine Antwort basiert auf einer Tabelle der Form:

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

Jede Zeile führt zu einer neuen Spalte, die zurückgegeben werden muss. Bei einem dynamischen Rückgabetyp wie diesem ist es kaum möglich, dies mit einem einzigen Aufruf der Datenbank vollständig dynamisch zu machen. Lösungen in zwei Schritten demonstrieren :

  1. Abfrage generieren
  2. Generierte Abfrage ausführen

Im Allgemeinen ist dies durch die maximale Anzahl von Spalten begrenzt, die eine Tabelle enthalten kann. Also keine Option für Tabellen mit mehr als 1600 Zeilen (oder weniger). Einzelheiten:

  • Was ist die maximale Anzahl von Spalten in einer PostgreSQL-Auswahlabfrage

Postgres 9.3 oder älter

Dynamische Lösung mit crosstab()

  • Vollständig dynamisch, funktioniert für jede Tabelle. Geben Sie den Tabellennamen in zwei an Orte:
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;

Könnte in eine Funktion mit einem einzigen Parameter verpackt werden ...
Erzeugt eine Abfrage der Form:

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)

Erzeugt das gewünschte Ergebnis:

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

Einfache Lösung mit 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;
  • Langsam für Tabellen mit mehr als ein paar Spalten.

Erzeugt eine Abfrage der Form:

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

Gleiches Ergebnis.

Postgres 9.4+

Dynamische Lösung mit crosstab()

Verwenden Sie dies, wenn Sie können. Schlagt den 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;

Betrieb mit attnum anstelle der tatsächlichen Spaltennamen. Einfacher und schneller. Verbinden Sie das Ergebnis mit pg_attribute noch einmal oder Spaltennamen integrieren wie im Beispiel pg 9.3.
Erzeugt eine Abfrage der Form:

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

Dies nutzt eine ganze Reihe von erweiterten Funktionen. Einfach zu viel zum Erklären.

Einfache Lösung mit unnest()

Ein unnest() kann jetzt mehrere Arrays parallel zum Entschachteln verwenden.

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;

Ergebnis:

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-Geige läuft auf Seite 9.3.