将表格转置,将列转换为行。

43
我有一个 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   ...

如何做到这一点?

2
我认为https://dev59.com/cWkv5IYBdhLWcg3wZwC-#10625294就是你要找的。 - Audrius Kažukauskas
请查看 "tablefunc" 贡献模块,它包含一个 "crosstab" 函数,应该可以做到你想要的。 - user330315
嘿,伙计,你试过什么了吗? - Praveen Kumar Purushothaman
嗨,我还没有尝试过交叉表函数......但我已经尝试了下面给出的答案,但仍然在寻找更多的东西。 - DonRaHulk
5个回答

41
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"

参考:将列标题转换为行


5
我会尽力为您翻译:+ this is the best imo在我看来,这是最好的。 - user39950

38
基于一个形式为表格的答案,我的回答如下:
CREATE TABLE tbl (
  sl_no int
, username text
, designation text
, salary int
);

每一行都会产生一个新的列返回。像这样具有动态返回类型的情况下,使用单个数据库调用完全实现动态返回几乎是不可能的。可以通过两个步骤来演示解决方案:

  1. 生成查询语句
  2. 执行生成的查询语句

通常,这受到表格可以容纳的最大列数的限制。因此,对于超过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

相同的结果。


这很棒...不过我还没有完全弄清楚 :). 注意,当列名中有特殊字符时,“带交叉表的动态解决方案”无法正常工作。 - shaunc
1
@shaunc:它应该适用于任何列名,因为它们都使用quote_ident(attname)进行转义。(但最好不要在列名中使用特殊字符。) - Erwin Brandstetter
尝试运行 create table tbl ("'" int); insert into tbl select 1; 然后运行代码片段 - 由于交叉表中引用的 SQL 中的单引号未转义,因此生成的 SQL 无法执行。注意:“最好不要有特殊字符” - 如果只是我的数据集就好了 :) - shaunc
1
quote_ident(attname) 更改为 btrim(quote_literal(quote_ident(attname)), '''') 可以使其正常工作... 但现在我收到了 ERROR invalid return type: DETAIL: SQL rowid datatype does not match return rowid datatype. 的错误提示。 - shaunc
1
@shaunc:我明白了,你说得对。问题在于外层引号。我用美元引用代替了单引号。为了防止列名中的任何可能的愚蠢,可以连接crosstab()的字符串参数并使用format()quote_literal()进行转义。还将attnum bigint更改为WITH ORDINALITY返回的bigint。总之:永远不要在标识符中使用单引号,那是个陷阱。 - Erwin Brandstetter
第一个解决方案是否可以配置为使用CTE而不是表格? - MrChadMWood

14

如果你像我一样需要从Bash脚本获取这些信息,请注意psql有一个简单的命令行开关,可以告诉它将表列输出为行:

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

-x选项是让psql将列作为行输出的关键。


6
在 psql 中,您可以使用 \x 切换“扩展显示”模式。 - isedwards

9

我有一个比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


1

在普通的SQL或PL/pgSQL中没有适当的方法来完成这个任务。

最好的方法是在应用程序中获取数据,然后进行处理。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接