基于一个形式为表格的答案,我的回答如下:
CREATE TABLE tbl (
sl_no int
, username text
, designation text
, salary int
);
每一行都会产生一个新的列返回。像这样具有动态返回类型的情况下,使用单个数据库调用完全实现动态返回几乎是不可能的。可以通过两个步骤来演示解决方案:
- 生成查询语句
- 执行生成的查询语句
通常,这受到表格可以容纳的最大列数的限制。因此,对于超过1600行(或更少)的表格来说,这不是一个选项。详情请参见:
Postgres 9.4+
crosstab()
的动态解决方案
如果可以,请使用第一个。胜过其他方法。
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 <b>tbl</b>) t)
|| ')' AS sql
FROM pg_attribute
WHERE attrelid = '<b>tbl</b>'::regclass
AND attnum > 0
AND NOT attisdropped
GROUP BY attrelid;
使用
attnum
而不是实际列名进行操作。更简单更快速。再次将结果与
pg_attribute
连接或者像pg 9.3示例中那样集成列名。
生成的查询形式如下:
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);
这个使用了一整套高级功能,解释起来太多了。
unnest()
的简单解决方案
现在一个 unnest()
可以同时处理多个数组进行展开。
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;
结果:
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);
db<>fiddle 这里
旧的sqlfiddle
Postgres 9.3或更早版本
使用crosstab()
的动态解决方案
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 <b>tbl</b>) t)
|| ')' AS sql
FROM pg_attribute
WHERE attrelid = '<b>tbl</b>'::regclass
AND attnum > 0
AND NOT attisdropped
GROUP BY attrelid;
可以用一个单一参数的函数进行封装...
生成以下形式的查询:
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);
产生所需的结果:
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
使用 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;
生成的查询语句形式如下:
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
相同的结果。