Reverse rows to columns with SQL

2

I need to make a select that shows rows as columns from a table, using the SQL language ie

- Table

DESC | ENERO | MARZO | JUNIO

A    |  80   |   73  |   84  
B    |  100  |  100  |  100

What I need is that you show me the query

DESC |   A   |   B   |

ENERO|  80   |  100  |   
MARZO|  73   |  100  |  
JUNIO|  84   |  100  |
    
asked by Manuel Rondon 07.12.2016 в 16:44
source

2 answers

1

There are a few ways to do it. But in all cases, it is assumed that you know in advance which columns you want to convert to rows ( ENERO , MARZO , JUNIO ), and which (and how many) are the values in the column DESC you want to convert to columns ( A , B ). If you wish for all this to be dynamic, I do not think it is possible with a simple SQL query.

The first stage is to convert:

DESC | ENERO | MARZO | JUNIO

A    |  80   |   73  |   84  
B    |  100  |  100  |  100

to the following intermediate result:

DESC |  MES  | VALOR

A    | ENERO |  80
A    | MARZO |  73
A    | JUNIO |  84
B    | ENERO |  100
B    | MARZO |  100
B    | JUNIO |  100

A good way to achieve this in PostgreSQL is by using the UNNEST function:

select t."desc", u.*
  from tabla t
  cross join unnest(
    array['ENERO', 'MARZO', 'JUNIO'], 
    array[t.enero, t.marzo, t.junio]
  ) with ordinality as u(mes, valor, sort_no)

You will notice that I use the clause WITH ORDINALITY to generate an additional column sort_no that serves to control the logical order of the months. So the result of the query has been:

DESC |  MES  | VALOR  | SORT_NO

A    | ENERO |  80    |   1
A    | MARZO |  73    |   2
A    | JUNIO |  84    |   3
B    | ENERO |  100   |   1
B    | MARZO |  100   |   2
B    | JUNIO |  100   |   3

With this intermediate result in hand, you can use a GROUP BY along with conditional aggregation to get your desired final result:

with cte as (
    select t."desc", u.*
      from tabla t
      cross join unnest(
        array['ENERO', 'MARZO', 'JUNIO'], 
        array[t.enero, t.marzo, t.junio]
      ) with ordinality as u(mes, valor, sort_no)
)
select mes as "desc",
       max(case when "desc" = 'A' then valor end) as A,
       max(case when "desc" = 'B' then valor end) as B
  from cte
 group by mes, sort_no
 order by sort_no

In PostgreSQL, you can also take advantage of the clause FILTER to apply the condition instead of CASE , although the latter is more standard:

with cte as (
    select t."desc", u.*
      from tabla t
      cross join unnest(
        array['ENERO', 'MARZO', 'JUNIO'], 
        array[t.enero, t.marzo, t.junio]
      ) with ordinality as u(mes, valor, sort_no)
)
select mes as "desc",
       max(valor) filter (where "desc" = 'A') as A,
       max(valor) filter (where "desc" = 'B') as B
  from cte
 group by mes, sort_no
 order by sort_no

Live Demo .

Another way to achieve the desired result is by using the CROSSTAB function, which is more or less equivalent to the PIVOT function that exists in other databases:

select mes as "desc", a, b
  from crosstab('select u.mes, u.sort_no, t."desc", u.valor
                   from tabla t
                  cross join unnest(
                    array[''ENERO'', ''MARZO'', ''JUNIO''], 
                    array[t.enero, t.marzo, t.junio]
                  ) with ordinality as u(mes, valor, sort_no)
                  order by 1',
                'select * from unnest(array[''A'', ''B''])')
       as ct(mes text, sort_no int, a int, b int)
order by sort_no

Please note that, to use the CROSSTAB function, you must ensure that the module tablefunc is installed in your database. This is the sentence needed to install the module, if necessary:

CREATE EXTENSION tablefunc;
    
answered by 09.12.2016 в 06:19
0

NOTE: Take this answer as an example only, because the required language is postgreSQL and the example I mention below applies to SQL , an apology for the confusion.

You want to do a pivot, it is grouped and with CASE:

declare @tabla table(
     [DESC] char,
     ENERO INT,
     MARZO INT,
     JUNIO INT
);

insert into @tabla values ('A',80,73,84);
insert into @tabla values ('B',100,100,100);


select mes,
  sum(case when [desc] = 'A' then value else 0 end) A,
  sum(case when [desc] = 'B' then value else 0 end) B
from
(
  select 1 id, [desc], ENERO value, 'ENERO' mes
  from @tabla
  union all
  select 3 id, [desc], MARZO value, 'MARZO' mes
  from @tabla
  union all
  select 5 id, [desc], JUNIO value, 'JUNIO' mes
  from @tabla
) src
group by id,mes
order by id

This is the way to do it "on foot", you can follow the example of the link that suggested @ Error404 to do it dynamically or with some other SQL functions like pivot. link

    
answered by 09.12.2016 в 01:23