1. 程式人生 > 資料庫 >PostgreSQL 實現列轉行問題

PostgreSQL 實現列轉行問題

1 測試表資料

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

2 列轉行寫法

寫法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

寫法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,CC

補充:PostgreSQL行列轉換(相容oracle pivot unpivot)

oracle11g開始內建了資料透視表pivot table這一功能,可以用來實現行列轉換的功能,但是在資料量較大的時候使用效能就會較差。

pivot語法為:

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

oracle pivot使用例子:

–建立測試表並插入資料

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,21,'math');
insert into usr values('c','phy');
insert into usr values('c',24,'math');
insert into usr values('d',25,'phy');

–使用pivot進行行列轉換

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

我們還可以使用unpivot來實現列轉行。

unpivot語法為:

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

oracle unpivot使用例子:

–建立測試表並插入資料

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

–使用unpivot進行列轉行

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.

那麼在pg中該如何實現oracle的pivot/unpivot的行列轉行功能呢?pg中自帶的tablefunc外掛可以實現,我們可以使用該外掛中的crosstab函式介面進行行列轉換。

pg行轉列例子:

–建表插入測試資料

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',02,234567);   
insert into tbl values ('test1',03,345678);   
insert into tbl values ('test1',04,05,567890);   
insert into tbl values ('test2',12);   
insert into tbl values ('test2',23);   
insert into tbl values ('test2',34);   
insert into tbl values ('test2',45);   
insert into tbl values ('test2',56);   
insert into tbl values ('test3',12);   
insert into tbl values ('test3',45);   
insert into tbl values ('test3',56);   
insert into tbl values ('test4',20);   
insert into tbl values ('test4',30);   
insert into tbl values ('test4',40);   
insert into tbl values ('test4',50);   
insert into tbl values ('test1',2019,567890);   
insert into tbl values ('test1',06,07,08,09,10,11,12,234567);   
insert into tbl values ('test2',23);
insert into tbl select * from tbl;  

–行轉列

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(#  -- 這個是需要進行行列變換的源SQL , 資料來源。  
bill(#  -- 排序欄位為group by欄位 ,最後一個欄位為轉換後的內容欄位,導數第二個欄位為行列變換的欄位(內容為列舉,比如月份)  
bill(#  -- (必須在下一個引數中提取出對應的所有列舉值)  
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(#  -- 行列轉換的行,有哪些值被提取出來作為列。 這個在這裡代表的是月份,也就是se_month的值   
bill(#  -- 或(select * from (values('jan'),...('dec')) t(se_month))  
bill(#  'select distinct se_month from tbl order by 1'     
bill(# )   
bill-# as  -- crosstab 輸出格式  
bill-# ( js jsonb,-- 第一個引數SQL內對應的order by對應的欄位(1個或多個)  
bill(#  Jan numeric,-- 第一個引數SQL內對應導數第二個欄位的列舉值,(行轉列)  
bill(#  feb numeric,-- ...同上  
bill(#  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)

–列轉行

bill=# with a as ( -- A對應原始資料(即需要列轉行的資料) 
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(#  -- 這個是需要進行行列變換的源SQL , 資料來源。  
bill(#  -- 排序欄位為group by欄位 ,最後一個欄位為轉換後的內容欄位,導數第二個欄位為行列變換的欄位(內容為列舉,比如月份)  
bill(#  -- (必須在下一個引數中提取出對應的所有列舉值)  
bill(#  $$select jsonb_build_object('seller',...('dec')) t(se_month))  
bill(#  'select distinct se_month from tbl order by 1'     
bill(# )   
bill(# as  -- crosstab 輸出格式  
bill(# ( js jsonb,bill(#  dec numeric  
bill(# )   
bill(# order by 1,2  
bill(# )  
bill-#,bill-# -- b , 用jsonb把多列合併為一列,並使用jsonb_each展開。 
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,(b.rec).key as month,(b.rec).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)

以上為個人經驗,希望能給大家一個參考,也希望大家多多支援我們。如有錯誤或未考慮完全的地方,望不吝賜教。