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 :
- Générer une requête
- 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.