SoFunction
Updated on 2025-04-08

PostgreSQL implements column conversion problem

1 Test table data

SELECT
  relative_label_content
FROM
  frk_s.label_cor_gene
relative_label_content
------
AA
BB
CC

2 columns to change the line

Writing method 1:

string_agg

SELECT
  frwybs,
  string_agg (relative_label_content, ',') as relative_label_content
FROM
  frk_s.label_cor_gene
GROUP BY
  frwybs
relative_label_content
------------
AA,BB,CC

Writing method 2:

array_to_string(ARRAY_AGG (text),',')

SELECT
  frwybs,
  array_to_string(
    ARRAY_AGG (DISTINCT relative_label_content),
    ','
  ) as labels_content
FROM
  frk_s.label_cor_gene
GROUP BY
  frwybs
labels_content
------------
AA,BB,CC

Supplement: PostgreSQL rank and column conversion (compatible with oracle pivot unpivot)

oracle11g has built-in pivot table function, which can be used to implement row and column conversion function, but the performance will be poor when the data volume is large.

The pivot syntax is:

SELECT ...  
FROM  ...  
PIVOT [XML]  
  (pivot_clause  
  pivot_for_clause  
  pivot_in_clause )  
WHERE ... 

Example of oracle pivot usage:

– Create a test table and insert data

create table usr
(name varchar2(20),
 score int,
 class varchar2(20)
);
insert into usr values('a',20,'math');
insert into usr values('a',22,'phy');
insert into usr values('b',23,'phy');
insert into usr values('b',21,'math');
insert into usr values('c',22,'phy');
insert into usr values('c',24,'math');
insert into usr values('d',25,'math');
insert into usr values('d',23,'phy');

– Use pivot for row and column conversion

SQL> select * from usr
 2 pivot(
sum(score)
for class in ('math','phy')
 3  4  5 );
NAME           'math'   'phy'
-------------------- ---------- ----------
d              25     23
a              20     22
b              21     23
c              24     22

We can also use unpivot to achieve column-to-line conversion.

The unpivot syntax is:

SELECT ...  
FROM ...  
UNPIVOT [INCLUDE|EXCLUDE NULLS]  
  (unpivot_clause  
  unpivot_for_clause  
  unpivot_in_clause )  
WHERE ... 

Example of oracle unpivot usage:

– Create a test table and insert data

CREATE TABLE t1 
(
  VendorID int, 
  Emp1 int, 
  Emp2 int, 
  Emp3 int, 
  Emp4 int,
  Emp5 int
); 
INSERT INTO t1 VALUES (1,4,3,5,4,4); 
INSERT INTO t1 VALUES (2,4,1,5,5,5); 
INSERT INTO t1 VALUES (3,4,3,5,4,4); 
INSERT INTO t1 VALUES (4,4,2,5,5,4); 
INSERT INTO t1 VALUES (5,5,1,5,5,5); 

–Use unpivot for column conversion

SQL> select * from t1
 2 UNPIVOT(
orders for Employee in(emp1,emp2,emp3,emp4,emp5)
); 3  4 
 VENDORID EMPL   ORDERS
---------- ---- ----------
     1 EMP1     4
     1 EMP2     3
     1 EMP3     5
     1 EMP4     4
     1 EMP5     4
     2 EMP1     4
     2 EMP2     1
     2 EMP3     5
     2 EMP4     5
     2 EMP5     5
     3 EMP1     4
 VENDORID EMPL   ORDERS
---------- ---- ----------
     3 EMP2     3
     3 EMP3     5
     3 EMP4     4
     3 EMP5     4
     4 EMP1     4
     4 EMP2     2
     4 EMP3     5
     4 EMP4     5
     4 EMP5     4
     5 EMP1     5
     5 EMP2     1
 VENDORID EMPL   ORDERS
---------- ---- ----------
     5 EMP3     5
     5 EMP4     5
     5 EMP5     5
25 rows selected.

So how to implement the rank-and-line conversion function of oracle's pivot/unpivot in pg? The tablefunc plugin that comes with pg can be implemented, and we can use the crossstab function interface in this plugin to perform row and column conversion.

Example of pg row to column:

– Create table and insert test data

create table tbl (seller text,se_year int,se_month int,se_amount int); 
insert into tbl values ('test1',2020,01,123456);   
insert into tbl values ('test1',2020,02,234567);   
insert into tbl values ('test1',2020,03,345678);   
insert into tbl values ('test1',2020,04,345678);   
insert into tbl values ('test1',2020,05,567890);   
insert into tbl values ('test2',2020,01,12);   
insert into tbl values ('test2',2020,02,23);   
insert into tbl values ('test2',2020,03,34);   
insert into tbl values ('test2',2020,04,45);   
insert into tbl values ('test2',2020,05,56);   
insert into tbl values ('test3',2020,03,12);   
insert into tbl values ('test3',2020,04,45);   
insert into tbl values ('test3',2020,05,56);   
insert into tbl values ('test4',2020,02,20);   
insert into tbl values ('test4',2020,03,30);   
insert into tbl values ('test4',2020,04,40);   
insert into tbl values ('test4',2020,05,50);   
insert into tbl values ('test1',2019,01,123456);   
insert into tbl values ('test1',2019,02,234567);   
insert into tbl values ('test1',2019,03,345678);   
insert into tbl values ('test1',2019,04,345678);   
insert into tbl values ('test1',2019,05,567890);   
insert into tbl values ('test1',2019,06,123456);   
insert into tbl values ('test1',2019,07,234567);   
insert into tbl values ('test1',2019,08,345678);   
insert into tbl values ('test1',2019,09,345678);   
insert into tbl values ('test1',2019,10,567890);   
insert into tbl values ('test1',2019,11,123456);   
insert into tbl values ('test1',2019,12,234567);   
insert into tbl values ('test2',2019,11,12);   
insert into tbl values ('test2',2019,12,23);
insert into tbl select * from tbl;  

– Row to column

bill=# select   
bill-#  js->>'seller' as seller,    
bill-#  js->>'se_year' as se_year,   
bill-#  jan ,  
bill-#  feb ,  
bill-#  mar ,  
bill-#  apr ,  
bill-#  may ,  
bill-#  jun ,  
bill-#  jul ,  
bill-#  aug ,  
bill-#  sep ,  
bill-#  oct ,  
bill-#  nov ,  
bill-#  dec   
bill-# from crosstab(  
bill(# -- This is the source SQL and data source that requires row and column transformation.bill(# -- The sorting field is group by field, the last field is the converted content field, and the second field of the derivative is the row and column transform field (the content is an enum, such as a month)bill(# -- (All corresponding enum values ​​must be extracted in the next parameter)bill(#  $$select jsonb_build_object('seller', seller, 'se_year', se_year) as js, se_month, sum(se_amount) from tbl group by 1,2 order by 1$$,    
bill(# -- For rows converted by rows and columns, which values ​​are extracted as columns.  This represents the month here, which is the value of se_monthbill(# -- or (select * from (values('jan'),...('dec')) t(se_month))bill(#  'select distinct se_month from tbl order by 1'     
bill(# )   
bill-# as -- crossstab output formatbill-# ( js jsonb, -- field corresponding to order by in the first parameter (1 or more)bill(# Jan numeric, -- The enumeration value of the second field corresponding to the derivative in the first parameter SQL (row to column)bill(# feb numeric, -- ...Same as abovebill(#  mar numeric,  
bill(#  apr numeric,  
bill(#  may numeric,  
bill(#  jun numeric,  
bill(#  jul numeric,  
bill(#  aug numeric,  
bill(#  sep numeric,  
bill(#  oct numeric,  
bill(#  nov numeric,  
bill(#  dec numeric  
bill(# )   
bill-# order by 1,2;  
 seller | se_year | jan  | feb  | mar  | apr  |  may  | jun  | jul  | aug  | sep  |  oct  | nov  | dec  
--------+---------+--------+--------+--------+--------+---------+--------+--------+--------+--------+---------+--------+--------
 test1 | 2019  | 246912 | 469134 | 691356 | 691356 | 1135780 | 246912 | 469134 | 691356 | 691356 | 1135780 | 246912 | 469134
 test1 | 2020  | 246912 | 469134 | 691356 | 691356 | 1135780 |    |    |    |    |     |    |    
 test2 | 2019  |    |    |    |    |     |    |    |    |    |     |   24 |   46
 test2 | 2020  |   24 |   46 |   68 |   90 |   112 |    |    |    |    |     |    |    
 test3 | 2020  |    |    |   24 |   90 |   112 |    |    |    |    |     |    |    
 test4 | 2020  |    |   40 |   60 |   80 |   100 |    |    |    |    |     |    |    
(6 rows)

– Column to row

bill=# with a as ( -- A corresponds to the original data (that is, the data that needs to be column-to-line)bill(# select   
bill(#  js->>'seller' as seller,    
bill(#  js->>'se_year' as se_year,   
bill(#  jan ,  
bill(#  feb ,  
bill(#  mar ,  
bill(#  apr ,  
bill(#  may ,  
bill(#  jun ,  
bill(#  jul ,  
bill(#  aug ,  
bill(#  sep ,  
bill(#  oct ,  
bill(#  nov ,  
bill(#  dec   
bill(# from crosstab(  
bill(# -- This is the source SQL and data source that requires row and column transformation.bill(# -- The sorting field is group by field, the last field is the converted content field, and the second field of the derivative is the row and column transform field (the content is an enum, such as a month)bill(# -- (All corresponding enum values ​​must be extracted in the next parameter)bill(#  $$select jsonb_build_object('seller', seller, 'se_year', se_year) as js, se_month, sum(se_amount) from tbl group by 1,2 order by 1$$,    
bill(# -- For rows converted by rows and columns, which values ​​are extracted as columns.  This represents the month here, which is the value of se_monthbill(# -- or (select * from (values('jan'),...('dec')) t(se_month))bill(#  'select distinct se_month from tbl order by 1'     
bill(# )   
bill(# as -- crossstab output formatbill(# ( js jsonb, -- field corresponding to order by in the first parameter (1 or more)bill(# Jan numeric, -- The enumeration value of the second field corresponding to the derivative in the first parameter SQL (row to column)bill(# feb numeric, -- ...Same as abovebill(#  mar numeric,  
bill(#  apr numeric,  
bill(#  may numeric,  
bill(#  jun numeric,  
bill(#  jul numeric,  
bill(#  aug numeric,  
bill(#  sep numeric,  
bill(#  oct numeric,  
bill(#  nov numeric,  
bill(#  dec numeric  
bill(# )   
bill(# order by 1,2  
bill(# )  
bill-# ,   
bill-# -- b , use jsonb to merge multiple columns into one column, and use jsonb_each to expand.bill-# b as (select seller, se_year, jsonb_each(row_to_json(a)::jsonb-'seller'::text-'se_year'::text) as rec from a)   
bill-# select seller, se_year, ().key as month, ().value as sum from b;  
 seller | se_year | month |  sum  
--------+---------+-------+---------
 test1 | 2019  | apr  | 691356
 test1 | 2019  | aug  | 691356
 test1 | 2019  | dec  | 469134
 test1 | 2019  | feb  | 469134
 test1 | 2019  | jan  | 246912
 test1 | 2019  | jul  | 469134
 test1 | 2019  | jun  | 246912
 test1 | 2019  | mar  | 691356
 test1 | 2019  | may  | 1135780
 test1 | 2019  | nov  | 246912
 test1 | 2019  | oct  | 1135780
 test1 | 2019  | sep  | 691356
 test1 | 2020  | apr  | 691356
 test1 | 2020  | aug  | null
 test1 | 2020  | dec  | null
 test1 | 2020  | feb  | 469134
 test1 | 2020  | jan  | 246912
 test1 | 2020  | jul  | null
 test1 | 2020  | jun  | null
 test1 | 2020  | mar  | 691356
 test1 | 2020  | may  | 1135780
 test1 | 2020  | nov  | null
 test1 | 2020  | oct  | null
 test1 | 2020  | sep  | null
 test2 | 2019  | apr  | null
 test2 | 2019  | aug  | null
 test2 | 2019  | dec  | 46
 test2 | 2019  | feb  | null
 test2 | 2019  | jan  | null
 test2 | 2019  | jul  | null
 test2 | 2019  | jun  | null
 test2 | 2019  | mar  | null
 test2 | 2019  | may  | null
 test2 | 2019  | nov  | 24
 test2 | 2019  | oct  | null
 test2 | 2019  | sep  | null
 test2 | 2020  | apr  | 90
 test2 | 2020  | aug  | null
 test2 | 2020  | dec  | null
 test2 | 2020  | feb  | 46
 test2 | 2020  | jan  | 24
 test2 | 2020  | jul  | null
 test2 | 2020  | jun  | null
 test2 | 2020  | mar  | 68
 test2 | 2020  | may  | 112
 test2 | 2020  | nov  | null
 test2 | 2020  | oct  | null
 test2 | 2020  | sep  | null
 test3 | 2020  | apr  | 90
 test3 | 2020  | aug  | null
 test3 | 2020  | dec  | null
 test3 | 2020  | feb  | null
 test3 | 2020  | jan  | null
 test3 | 2020  | jul  | null
 test3 | 2020  | jun  | null
 test3 | 2020  | mar  | 24
 test3 | 2020  | may  | 112
 test3 | 2020  | nov  | null
 test3 | 2020  | oct  | null
 test3 | 2020  | sep  | null
 test4 | 2020  | apr  | 80
 test4 | 2020  | aug  | null
 test4 | 2020  | dec  | null
 test4 | 2020  | feb  | 40
 test4 | 2020  | jan  | null
 test4 | 2020  | jul  | null
 test4 | 2020  | jun  | null
 test4 | 2020  | mar  | 60
 test4 | 2020  | may  | 100
 test4 | 2020  | nov  | null
 test4 | 2020  | oct  | null
 test4 | 2020  | sep  | null
(72 rows)

The above is personal experience. I hope you can give you a reference and I hope you can support me more. If there are any mistakes or no complete considerations, I would like to give you advice.