1

I have a table in PostgreSQL like:

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

I need to transpose it to:

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

I found next solution on stackoverflow:

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

But it doesn't work with syntax error about 'for'. Where I'm wrong?

Erwin Brandstetter
  • 605,456
  • 145
  • 1,078
  • 1,228
user2401432
  • 23
  • 2
  • 4

2 Answers2

0

I've not been able to get your approach to work yet; but this multistep union seems to.

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

Maybe you would find unnest more palpable:

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
xQbert
  • 34,733
  • 2
  • 41
  • 62
  • I have 12 columns (monthes) and 10-12 rows with organizations, but this list will be expanded, is there more simple way than write the names of each column and row right in the code? – user2401432 Sep 18 '17 at 15:30
0

Actually, you need something like unpivot plus pivot here.

1. decompose the rows to get single entries per month ("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;

If there are more organizations, you have to add a WHERE clause to pick the relevant ones:

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

2., feed this to crosstab() to get your desired result:

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

Voilá.

The crosstab() function is provided by the additional module tablefunc, so this has to be installed.
Detailed instructions and links:

Automation:

Here is a basic function to build the SQL statement for any list of organizations and any list of months:

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;

You can extract the list of organizations or months dynamically, too ...

Call:

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

Produces above statement - which you can execute in turn.

Community
  • 1
  • 1
Erwin Brandstetter
  • 605,456
  • 145
  • 1,078
  • 1,228