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;