PostgreSQL 将列转换为行?转置?
Posted
技术标签:
【中文标题】PostgreSQL 将列转换为行?转置?【英文标题】:PostgreSQL convert columns to rows? Transpose? 【发布时间】:2012-12-14 14:29:33 【问题描述】:我有一个 PostgreSQL 函数(或表),它给我以下输出:
Sl.no username Designation salary etc..
1 A XYZ 10000 ...
2 B RTS 50000 ...
3 C QWE 20000 ...
4 D HGD 34343 ...
现在我想要输出如下:
Sl.no 1 2 3 4 ...
Username A B C D ...
Designation XYZ RTS QWE HGD ...
Salary 10000 50000 20000 34343 ...
如何做到这一点?
【问题讨论】:
我认为***.com/a/10625294/1870151 是您要找的。span> 查看 contrib 模块“tablefunc”,它包含一个“crosstab”函数,它应该可以满足您的需求。 嘿伙计,你到底试过什么? 嗨,我还没有尝试过交叉表功能......但我已经尝试了下面给出的答案,但仍在寻找更多的东西。 【参考方案1】: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"
参考:convertingColumnsToRows
【讨论】:
+ 这是最好的 imo【参考方案2】:在纯 SQL 或 PL/pgSQL 中没有适当的方法来执行此操作。
在从数据库中获取数据的应用程序中执行此操作会更好。
【讨论】:
【参考方案3】:我的答案基于以下表格:
CREATE TABLE tbl (
sl_no int
, username text
, designation text
, salary int
);
每一行都会产生一个要返回的新列。使用像这样的动态返回类型,几乎不可能通过一次调用数据库来使其完全动态化。用两个步骤演示解决方案:
-
生成查询
执行生成的查询
通常,这受到表可以容纳的最大列数的限制。因此对于超过 1600 行(或更少)的表来说不是一个选项。详情:
What is the maximum number of columns in a PostgreSQL select queryPostgres 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 tbl) t)
|| ')' AS sql
FROM pg_attribute
WHERE attrelid = 'tbl'::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
同样的结果。
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 tbl) t)
|| ')' AS sql
FROM pg_attribute
WHERE attrelid = 'tbl'::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)
SQL Fiddle 在 pg 9.3 上运行。
【讨论】:
这太棒了......虽然还没有完全弄清楚:)。请注意,当列名中包含特殊字符时,“使用交叉表的动态解决方案”不起作用。 @shaunc:它应该适用于 any 列名,因为它们是用quote_ident(attname)
转义的。 (但最好不要在列名中以特殊字符开头。)
尝试create table tbl ("'" int); insert into tbl select 1;
然后运行sn-p——生成的sql 不可执行,因为交叉表引用的sql 中的单引号未转义。注意“最好不要有特殊字符”——如果它是我的数据集就好了。 :)
将quote_ident(attname)
更改为btrim(quote_literal(quote_ident(attname)), '''')
可以正常工作...尽管现在我得到ERROR invalid return type: DETAIL: SQL rowid datatype does not match return rowid datatype.
@shaunc:我明白了,你是对的。问题在于引号的外层。我用美元报价替换了单引号。为了防止列名中任何可能的愚蠢行为,您可以连接crosstab()
的字符串参数并使用format()
或quote_literal()
对其进行转义。也更改为attnum bigint
,因为WITH ORDINALITY
返回bigint
。除了所有这些:从不在标识符中使用单引号,那是一个加载的footgun。【参考方案4】:
如果(像我一样)您需要来自 bash 脚本的这些信息,请注意,psql 有一个简单的命令行开关,可以告诉它将表列输出为行:
psql mydbname -x -A -F= -c "SELECT * FROM foo WHERE id=123"
-x
选项是让 psql 将列输出为行的关键。
【讨论】:
在 psql 中,您可以使用\x
切换“扩展显示”【参考方案5】:
我有一个比 Erwin 上面指出的更简单的方法,即我使用 Postgres 的那个工人(我认为它应该适用于所有支持 SQL 标准的主要关系数据库)
您可以简单地使用 UNION 代替交叉表:
SELECT text 'a' AS "text" UNION SELECT 'b';
text
------
a
b
(2 rows)
当然,这取决于您要应用它的情况。考虑到您事先知道需要哪些字段,即使查询不同的表也可以采用这种方法。即:
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)
恕我直言,这是一种更易于维护的方法。查看此页面了解更多信息:https://www.postgresql.org/docs/current/typeconv-union-case.html
【讨论】:
以上是关于PostgreSQL 将列转换为行?转置?的主要内容,如果未能解决你的问题,请参考以下文章