作過數據清洗ETL工做的都知道,行列轉換是一個常見的數據整理需求。在不一樣的編程語言中有不一樣的實現方法,好比SQL中使用case+group,或者Power BI的M語言中用拖放組件實現。今天正好須要在PostgreSQL中處理一個數據行列轉換,就把這個方法記錄下來。html
首先明確一下啥叫行列轉換,由於這個叫法也不是很統一,有的地方叫轉置,有的地方叫透視,不一而足。咱們就如下表爲例,定義以下:算法
表1sql
項目 | 月份 | 金額 |
---|---|---|
A | 1 | 10 |
A | 2 | 20 |
A | 2 | 30 |
B | 1 | 30 |
表2編程
項目 | 1月 | 2月 |
---|---|---|
A | 10 | 50 |
B | 30 |
構造一個表以行格式保存數據json
drop table if exists demo.tf_pivot;
create table demo.tf_pivot (
city text,
year integer,
month integer,
income integer
);
insert into demo.tf_pivot values('a', 2018, 1, 1);
insert into demo.tf_pivot values('a', 2018, 2, 1);
insert into demo.tf_pivot values('a', 2018, 3, 1);
insert into demo.tf_pivot values('a', 2018, 4, 1);
insert into demo.tf_pivot values('a', 2019, 1, 2);
insert into demo.tf_pivot values('a', 2019, 2, 2);
insert into demo.tf_pivot values('a', 2019, 3, 2);
insert into demo.tf_pivot values('a', 2019, 4, 2);
insert into demo.tf_pivot values('b', 2018, 1, 3);
insert into demo.tf_pivot values('b', 2018, 2, 3);
insert into demo.tf_pivot values('b', 2018, 3, 3);
insert into demo.tf_pivot values('b', 2018, 4, 3);
insert into demo.tf_pivot values('b', 2019, 1, 4);
insert into demo.tf_pivot values('b', 2019, 2, 4);
insert into demo.tf_pivot values('b', 2019, 3, 4);
insert into demo.tf_pivot values('b', 2019, 4, 4);
複製代碼
構造一個表以列格式保存數據編程語言
drop table if exists demo.tf_unpivot;
create table demo.tf_unpivot(
city text,
year int,
m01 int,
m02 int,
m03 int,
m04 int
);
insert into demo.tf_unpivot values('a', 2018, 1,2,3,4);
insert into demo.tf_unpivot values('a', 2019, 10,20,30,40);
insert into demo.tf_unpivot values('b', 2018, 100,200,300,400);
複製代碼
首先展現一下傳統的case when語法。在PostgreSQL中,還提供了一個filter語法(簡化case when)函數
-- case when , 在PG中可使用filter
select
city,
sum(income) filter (where year=2018) as "2018",
sum(income) filter (where year=2019) as "2019"
from demo.tf_pivot
group by city
order by city;
-- 結果
city | 2018 | 2019
------+------+------
a | 4 | 8
b | 12 | 16
(2 rows)
複製代碼
在PostgreSQL中,若是安裝了tablefunc擴展,就可使用crosstab()函數來簡化pivot操做。crosstab()提供了多個版本,這裏僅演示最基礎的版本 crosstab(sql text, sql text)。post
crosstab()透視操做簡單直接,關鍵點說明以下ui
代碼以下spa
--
select * from crosstab(
-- 基礎查詢,返回X,Y,V格式的數據集
'select city,year,sum(income) from demo.tf_pivot group by city,year order by 1, 2',
-- 產生水平表頭的查詢
'select distinct year from demo.tf_pivot order by 1')
-- 由於crosstab()返回的結果是不能動態肯定的,因此須要指定字段名稱和類型
as ("city" text,
"y2018" int, -- 這裏的類型是字段內容的類型,不是表頭
"y2019" int);
複製代碼
結果以下
city | y2018 | y2019
------+-------+-------
a | 4 | 8
b | 12 | 16
(2 rows)
複製代碼
在實際應用中,可能咱們須要的最終結果包括多維數據,好比下面這種,行上面有兩個維度,分別是city和year,而後對month進行透視。(這個結果和咱們構造的樣例表demo.tf_unpivot是同樣的)
city | year | m01 | m02 | m03 | m04
------+------+-----+-----+-----+-----
a | 2018 | 1 | 1 | 1 | 1
a | 2019 | 2 | 2 | 2 | 2
b | 2018 | 3 | 3 | 3 | 3
b | 2019 | 4 | 4 | 4 | 4
(4 rows)
複製代碼
由於原生crosstab僅能支持X,Y兩個維度,因此咱們設計一個取巧算法來達到目的
--
select
-- 把拼接字段進行拆分
split_part(city_year, '~', 1) as city,
split_part(city_year, '~', 2)::int as year,
m01,m02,m03,m04
from crosstab(
-- 把多個字段拼接爲一個,而後執行聚合操做
'select city || ''~'' || year, month, sum(income) from demo.tf_pivot group by city || ''~'' || year, month order by 1,2,3',
--
'select distinct month from demo.tf_pivot order by 1')
as ("city_year" text, -- 結果集中指定拼接字段類型爲text
"m01" int,
"m02" int,
"m03" int,
"m04" int);
複製代碼
PostgreSQL沒有提供函數來實現unpivot操做,不過咱們可使用PG提供的幾個高級功能來間接實現需求。須要用到的函數和語法包括:
原始數據以下
select * from demo.tf_unpivot;
city | year | m01 | m02 | m03 | m04
------+------+-----+-----+-----+-----
a | 2018 | 1 | 2 | 3 | 4
a | 2019 | 10 | 20 | 30 | 40
b | 2018 | 100 | 200 | 300 | 400
(3 rows)
複製代碼
代碼以下
--
select
r.city,
r.year,
key as month_str,
substr(key, 2)::int as month_int, -- 把 m01 轉換成 1
value::int as income
from (select city, year, row_to_json(t.*) as line from demo.tf_unpivot t) as r
join lateral json_each_text(r.line) on (key ~* '^m[0-9]+');
複製代碼
結果以下
city | year | month_str | month_int | income
------+------+-----------+-----------+--------
a | 2018 | m01 | 1 | 1
a | 2018 | m02 | 2 | 2
a | 2018 | m03 | 3 | 3
a | 2018 | m04 | 4 | 4
a | 2019 | m01 | 1 | 10
a | 2019 | m02 | 2 | 20
a | 2019 | m03 | 3 | 30
a | 2019 | m04 | 4 | 40
b | 2018 | m01 | 1 | 100
b | 2018 | m02 | 2 | 200
b | 2018 | m03 | 3 | 300
b | 2018 | m04 | 4 | 400
(12 rows)
複製代碼