PostgreSQL
 sql >> Base de données >  >> RDS >> PostgreSQL

PostgreSQL convertit les colonnes en lignes ? Transposer?

En basant ma réponse sur un tableau de la forme :

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

Chaque ligne donne lieu à une nouvelle colonne à renvoyer. Avec un type de retour dynamique comme celui-ci, il n'est guère possible de le rendre complètement dynamique avec un seul appel à la base de données. Démonstration de solutions en deux étapes :

  1. Générer une requête
  2. Exécuter la requête générée

Généralement, cela est limité par le nombre maximum de colonnes qu'une table peut contenir. Donc pas une option pour les tables avec plus de 1600 lignes (ou moins). Détails :

  • Quel est le nombre maximum de colonnes dans une requête de sélection PostgreSQL

Postgres 9.3 ou version antérieure

Solution dynamique avec crosstab()

  • Complètement dynamique, fonctionne pour n'importe quelle table. Indiquez le nom de la table en deux lieux :
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;

Peut être enveloppé dans une fonction avec un seul paramètre ...
Génère une requête de la forme :

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)

Produit le résultat souhaité :

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

Solution simple avec 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;
  • Lent pour les tableaux comportant plusieurs colonnes.

Génère une requête de la forme :

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

Même résultat.

Postgres 9.4+

Solution dynamique avec crosstab()

Utilisez-le si vous le pouvez. Bat le reste.

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;

Fonctionnement avec attnum au lieu des noms de colonnes réels. Plus simple et plus rapide. Joignez le résultat à pg_attribute une fois de plus ou intégrez les noms de colonnes comme dans l'exemple pg 9.3.
Génère une requête de la forme :

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

Cela utilise toute une gamme de fonctionnalités avancées. Trop de choses à expliquer.

Solution simple avec unnest()

Un unnest() peut maintenant prendre plusieurs baies à désimbriquer en parallèle.

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;

Résultat :

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)

Violon SQL fonctionnant à la page 9.3.