【问题标题】:PostgreSQL convert columns to rows? Transpose?PostgreSQL 将列转换为行?转置?
【发布时间】: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   ...

如何做到这一点?

【问题讨论】:

  • 我认为stackoverflow.com/a/10625294/1870151 是您要找的。​​span>
  • 查看 contrib 模块“tablefunc”,它包含一个“crosstab”函数,它应该可以满足您的需求。
  • 嘿伙计,你到底试过什么?
  • 嗨,我还没有尝试过交叉表功能......但我已经尝试了下面给出的答案,但仍在寻找更多的东西。

标签: sql postgresql pivot-table dynamic-sql 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
    );
    

    每一行都会产生一个要返回的新列。使用像这样的动态返回类型,几乎不可能通过一次调用数据库来使其完全动态化。用两个步骤演示解决方案:

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

    通常,这受到表可以容纳的最大列数的限制。因此对于超过 1600 行(或更少)的表来说不是一个选项。详情:

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

    【讨论】:

      猜你喜欢
      • 2017-09-18
      • 2015-09-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-10-15
      • 1970-01-01
      • 2012-05-24
      相关资源
      最近更新 更多