web-dev-qa-db-fra.com

PostgreSQL convertir des colonnes en lignes? Transposer?

J'ai une fonction (ou table) PostgreSQL qui me donne la sortie suivante:

Sl.no    username    Designation    salary   etc..
 1        A           XYZ            10000    ...
 2        B           RTS            50000    ...
 3        C           QWE            20000    ...
 4        D           HGD            34343    ...

Maintenant, je veux la sortie comme ci-dessous:

Sl.no            1       2        3       4       ...
 Username        A       B        C       D       ...
 Designation     XYZ     RTS      QWE     HGD     ...
 Salary          10000   50000    20000   34343   ...

Comment faire ça?

24
DonRaHulk

Baser ma réponse sur un tableau du formulaire:

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

Chaque ligne entraîne 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

En règle générale, cela est limité par le nombre maximal de colonnes qu'une table peut contenir. Ce n'est donc pas une option pour les tableaux de plus de 1600 lignes (ou moins). Détails:

Postgres 9.3 ou plus

Solution dynamique avec crosstab()

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

Pourrait être enveloppé dans une fonction avec un seul paramètre ...
Génère une requête du formulaire:

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 avec plus de deux colonnes.

Génère une requête du formulaire:

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;

Fonctionnant 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 des noms de colonne comme dans l'exemple pg 9.3.
Génère une requête du formulaire:

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 à expliquer.

Solution simple avec unnest()

Un unnest() peut désormais prendre plusieurs tableaux pour être imbriqués 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)

SQL Fiddle exécuté sur pg 9.3.

27
Erwin Brandstetter
SELECT
   unnest(array['Sl.no', 'username', 'Designation','salary']) AS "Columns",
   unnest(array[Sl.no, username, value3Count,salary]) AS "Values"
FROM view_name
ORDER BY "Columns"

Référence: convertingColumnsToRows

12
Mari

Si (comme moi) vous aviez besoin de ces informations à partir d'un script bash, notez qu'il existe un simple commutateur de ligne de commande pour psql pour lui indiquer de sortir les colonnes du tableau sous forme de lignes:

psql mydbname -x -A -F= -c "SELECT * FROM foo WHERE id=123"

L'option -x Est la clé pour que psql affiche les colonnes sous forme de lignes.

9
Stéphane

J'ai une approche plus simple que Erwin a indiqué ci-dessus, ce travailleur pour moi avec Postgres (et je pense que cela devrait fonctionner avec toutes les principales bases de données relationnelles dont le support SQL standard)

Vous pouvez utiliser simplement UNION au lieu du tableau croisé:

SELECT text 'a' AS "text" UNION SELECT 'b';

 text
------
 a
 b
(2 rows)

Bien sûr, cela dépend du cas dans lequel vous allez appliquer cela. Étant donné que vous savez à l'avance quels champs vous avez besoin, vous pouvez adopter cette approche même pour interroger différentes tables. C'est à dire.:

SELECT 'My first metric' as name, count(*) as total from first_table UNION
SELECT 'My second metric' as name, count(*) as total from second_table 

 name             | Total
------------------|--------
 My first metric  |     10
 My second metric |     20
(2 rows)

C'est une approche plus maintenable, à mon humble avis. Regardez cette page pour plus d'informations: https://www.postgresql.org/docs/current/typeconv-union-case.html

2
gbferreira

Il n'existe aucun moyen approprié de le faire en SQL simple ou en PL/pgSQL.

Il sera préférable de le faire dans l'application, qui récupère les données de la base de données.

1
Igor Romanchenko