PostgreSQL_行列转换pivot_unpivot

栏目: 数据库 · 发布时间: 6年前

内容简介:做过数据清洗ETL工作的都知道,行列转换是一个常见的数据整理需求。在不同的编程语言中有不同的实现方法,比如SQL中使用case+group,或者Power BI的M语言中用拖放组件实现。今天正好需要在PostgreSQL中处理一个数据行列转换,就把这个方法记录下来。首先明确一下啥叫表1

做过数据清洗ETL工作的都知道,行列转换是一个常见的数据整理需求。在不同的编程语言中有不同的实现方法,比如 SQL 中使用case+group,或者Power BI的M语言中用拖放组件实现。今天正好需要在PostgreSQL中处理一个数据行列转换,就把这个方法记录下来。

首先明确一下啥叫 行列转换 ,因为这个叫法也不是很统一,有的地方叫转置,有的地方叫透视,不一而足。我们就以下表为例,定义如下:

  • 从表1这种变成表2这种,叫 透视(pivot)
  • 反之叫 逆透视(unpivot)

表1

项目 月份 金额
A 1 10
A 2 20
A 2 30
B 1 30

表2

项目 1月 2月
A 10 50
B 30

构造样本数据

构造一个表以 格式保存数据

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);
复制代码

透视Pivot

CASE语法

首先展示一下传统的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)
复制代码

CROSSTAB语法

在PostgreSQL中,如果安装了tablefunc扩展,就可以使用crosstab()函数来简化pivot操作。crosstab()提供了多个版本,这里仅演示最基础的版本 crosstab(sql text, sql text)。

基础用法

crosstab()透视操作简单直接,关键点说明如下

  • 第一个参数,带有按X,Y汇总的SQL子句,返回X,Y,Value格式的数据集;
  • 第二个参数,SQL子句,返回用于水平表头中透视内容的所有值;
  • 使用AS子句明确指定返回的每一个字段名称和类型;

代码如下

-- 
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两个维度,所以我们设计一个取巧算法来达到目的

  • 在X上构造一个由多个字段构成的字段;
  • crosstab透视;
  • 使用split_part()函数把组合字段拆分为多个结果字段;
-- 
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);
复制代码

逆透视Unpivot

PostgreSQL没有提供函数来实现unpivot操作,不过我们可以使用PG提供的几个高级功能来间接实现需求。需要用到的函数和语法包括:

  • row_to_json() 把行数据转换为json串;
  • json_each_text() 把最外层的json对象转换成Key/Value,每个对象一行;
  • lateral 独立子查询内支持JOIN子查询外面的表(这个还没有搞太明白 :-D)

原始数据如下

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)
复制代码

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Python Algorithms

Python Algorithms

Magnus Lie Hetland / Apress / 2010-11-24 / USD 49.99

Python Algorithms explains the Python approach to algorithm analysis and design. Written by Magnus Lie Hetland, author of Beginning Python, this book is sharply focused on classical algorithms, but it......一起来看看 《Python Algorithms》 这本书的介绍吧!

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换