PostgreSQL中的转置表

Posted

技术标签:

【中文标题】PostgreSQL中的转置表【英文标题】:Transpose table in PostgreSQL 【发布时间】:2017-09-18 14:16:50 【问题描述】:

我在 PostgreSQL 中有一个表,例如:

org_name | month_1 | month_2 | ... | month_12
---------------------------------------------
org1     |  20     |   30    | ... |  15
org2     |  34     |   27    | ... |  49

我需要将其转置为:

month  |  org1 | org2 
----------------------
   1   |   20  | 34
   2   |   30  | 27
..     |   ..  | ..
  12   |   15  | 49

我在 *** 上找到了下一个解决方案:

SELECT
    *
FROM 
    (select org_name, monthes, value
    from my_table
    unpivot
    (
        value
        for monthes in (month_2, month_3, month_4, month_5, month_6, month_7, month_8, month_9, month_10, month_11, month_12)
    ) unpiv
    ) src
    pivot
    ( 
        sum(value)
        for org_name in ('org1', 'org2')
    ) piv

但它不适用于有关“for”的语法错误。我哪里错了?

【问题讨论】:

同样的结果=( 那么你有答案了吗? 【参考方案1】:

我还没能用你的方法来工作;但这个多步联合似乎是。

cte (mo, Org1, Org2) as (
SELECT 1, case when org_name = 'org1' then month_1 end as Org1, case when org_name = 'org2' then month_1 end as Org2 from myTable UNION ALL
SELECT 2, case when org_name = 'org1' then month_2 end as Org1, case when org_name = 'org2' then month_2 end as Org2 from myTable UNION ALL
SELECT 3, case when org_name = 'org1' then month_3 end as Org1, case when org_name = 'org2' then month_3 end as Org2 from myTable UNION ALL
...
SELECT 12, case when org_name = 'org1' then month_12 end as Org1, case when org_name = 'org2' then month_12 end as Org2 from myTable)
SELECT mo, max(org1) org1, max(org2) org2 
FROM  cte
GROUP BY mo

也许你会发现松散更明显:

With myTable (org_name, month_1, Month_2, month_3, Month_12) as (
    Select 'org1',20,30,40,15 union all
    Select 'org2',34,27,45,49),

cte  (Mo,Org1,org2) as(select unnest(ARRAY[1,2,3,12]) AS Mo
     , case when org_name='org1' then unnest(ARRAY[month_1, Month_2,month_3,Month_12]) end as Org1
     , case when org_name='org2' then unnest(ARRAY[month_1, Month_2,month_3,Month_12]) end as Org2
     from mytable)

     Select mo,sum(org1) org1, sum(org2) org2
     From cte
     group by mo

【讨论】:

我有 12 列(月)和 10-12 行的组织,但是这个列表会被扩展,有没有比在代码中直接写每个列和行的名称更简单的方法? 【参考方案2】:

实际上,您在这里需要类似 unpivot plus pivot 的东西。

1. 分解行以获得每个月的单个条目(“unpivot”):

SELECT mon, org_name, val
FROM   tbl, LATERAL (
   VALUES
      (1, month_1)
    , (2, month_2)
    -- ... more
    , (12, month_12)
   ) x(mon, val)
ORDER  BY 1, 2;

如果有更多的组织,你必须添加一个WHERE 子句来选择相关的:

WHERE  org_name IN ('org1', 'org2')

2.,将此反馈给crosstab() 以获得您想要的结果:

SELECT *
FROM   crosstab($$
      SELECT mon, org_name, val
      FROM   tbl, LATERAL (
         VALUES
            (1, month_1)
          , (2, month_2)
          -- ... more
          , (12, month_12)
         ) x(mon, val)
      WHERE  org_name IN ('org1', 'org2')
      ORDER  BY 1, 2$$)
   AS ct (month int, org1 int, org2 int);

瞧。

crosstab() 功能由附加模块 tablefunc 提供,因此必须安装。 详细说明和链接:

PostgreSQL Crosstab Query SELECT DISTINCT on multiple columns Convert one row into multiple rows with fewer columns

自动化:

这是为任何组织列表和任何月份列表构建 SQL 语句的基本函数:

CREATE OR REPLACE FUNCTION f_unpivot_colums(_orgs text[], _months int[])
  RETURNS text AS
$func$
SELECT 'SELECT *
FROM   crosstab($$
   SELECT x.mon, t.org_name, x.val
   FROM   tbl t, LATERAL (
      VALUES '
       || string_agg(format('(%s, t.%I)', m, 'month_' || m), ', ')
       || ') x(mon, val)
   WHERE  t.org_name = ANY (' || quote_literal(_orgs) || ')
   ORDER  BY 1, 2$$)
AS ct (month int, org1 int, org2 int)'
FROM   unnest(_months) m
$func$  LANGUAGE sql;

您也可以动态提取组织或月份列表...

呼叫:

SELECT f_unpivot_colums('org1, org2', '1,2,12')

产生上述语句 - 您可以依次执行。

【讨论】:

以上是关于PostgreSQL中的转置表的主要内容,如果未能解决你的问题,请参考以下文章

使用幂查询转置表

转置表,包括标题

PostgreSQL 将列转换为行?转置?

SQL转置表 - sqlserver [重复]

Postgresql 查询将列转置为行

在 QSqlQueryModel 中交换标题(转置表)