暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

PostgreSQL Oracle 兼容性 - 行列变换(pivot unpivot) (tablefunc, crosstab)

digoal 2018-07-14
1268

作者

digoal

日期

2018-07-14

标签

PostgreSQL , Oracle , 行列变换 , pivot , unpivot , tablefunc , crosstab , json , jsonb


背景

行列转换是OLAP应用场景中,对数据透视常用的SQL之一。

Oracle pivot 行转列

语法如下:

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

In addition tothe new PIVOT keyword, we can see three new pivot clauses, described below.

(1)pivot_clause: definesthe columns to be aggregated (pivot is an aggregate operation);

(2)pivot_for_clause: definesthe columns to be grouped and pivoted;

(3)pivot_in_clause: definesthe filter for the column(s) in the pivot_for_clause (i.e. the range of valuesto limit the results to). The aggregations for each value in thepivot_in_clause will be transposed into a separate column (where appropriate).

对pivot_for_clause 指定的列进行过滤,只将指定的行转成列。

如:

SQL> WITH pivot_data AS ( -- 使用CTE语法,行转列的记录集 2 SELECT deptno, job, sal -- 原始表,注意出现过的列,看PIVOT里面是如何处理的 3 FROM emp 4 ) 5 SELECT * 6 FROM pivot_data 7 PIVOT ( 8 SUM(sal) --<-- pivot_clause , 作为聚合项 9 FOR deptno --<-- pivot_for_clause 行转列字段(聚合) 10 IN (10,20,30,40) --<-- pivot_in_clause 行转列的行取值,最后会生成这些行(聚合) 11 );

未写入pivot内的列(JOB),被作为group by的字段。sal作为聚合列,deptno的内容10,20,30,40作为行列转换(GROUP BY)字段。

```
JOB 10 20 30 40


CLERK 1430 2090 1045
SALESMAN 6160
PRESIDENT 5500
MANAGER 2695 3272.5 3135
ANALYST 6600
5 rows selected.
```

Oracle unpivot 列转行

语法:

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

The unpivot_clause rotatescolumns into rows.

(1)The INCLUDE | EXCLUDE NULLS clausegives you the option of including or excluding null-valued rows. INCLUDE NULLS causesthe unpivot operation to include null-valued rows; EXCLUDE NULLS eliminatesnull-values rows from the return set. If you omit this clause, then the unpivotoperation excludes nulls.

这个选项用来控制unpivot 是否包含null 的记录,默认是不包含nulls的。

(2)unpivot_clause: this clause specifies a name for a column to represent the unpivotedmeasure values.

对应的具体值

(3)Inthe pivot_for_clause, specify a name for eachoutput column that will hold descriptor values, such as quarter or product.

对应转换后列的名称

(4)Inthe unpivot_in_clause, specify the input datacolumns whose names will become values in the output columns of the pivot_for_clause.These input data columns have names specifying a category value, such as Q1,Q2, Q3, Q4. The optional AS clause lets you map the input data columnnames to the specified literal values in the output columns.

具体列到行的列名

如:
```
SQL> SELECT *
2 FROM pivoted_data
3 UNPIVOT (
4 deptsal --<-- unpivot_clause
5 FOR saldesc --<-- unpivot_for_clause
6 IN (d10_sal, d20_sal, d30_sal, d40_sal) --<-- unpivot_in_clause
7 );

JOB SALDESC DEPTSAL


CLERK D10_SAL 1430
CLERK D20_SAL 2090
CLERK D30_SAL 1045
SALESMAN D30_SAL 6160
PRESIDENT D10_SAL 5500
MANAGER D10_SAL 2695
MANAGER D20_SAL 3272.5
MANAGER D30_SAL 3135
ANALYST D20_SAL 6600
```

PostgreSQL 行转列

https://www.postgresql.org/docs/devel/static/tablefunc.html

create extension tablefunc;

使用crosstab函数接口进行行列转换。

create table tbl_sellers_info (seller text,se_year int,se_month int,se_amount int); insert into tbl_sellers_info values ('德哥',2011,01,123456); insert into tbl_sellers_info values ('德哥',2011,02,234567); insert into tbl_sellers_info values ('德哥',2011,03,345678); insert into tbl_sellers_info values ('德哥',2011,04,345678); insert into tbl_sellers_info values ('德哥',2011,05,567890); insert into tbl_sellers_info values ('贝克汉姆',2011,01,12); insert into tbl_sellers_info values ('贝克汉姆',2011,02,23); insert into tbl_sellers_info values ('贝克汉姆',2011,03,34); insert into tbl_sellers_info values ('贝克汉姆',2011,04,45); insert into tbl_sellers_info values ('贝克汉姆',2011,05,56); insert into tbl_sellers_info values ('卡洛斯',2011,03,12); insert into tbl_sellers_info values ('卡洛斯',2011,04,45); insert into tbl_sellers_info values ('卡洛斯',2011,05,56); insert into tbl_sellers_info values ('罗纳尔多',2011,02,20); insert into tbl_sellers_info values ('罗纳尔多',2011,03,30); insert into tbl_sellers_info values ('罗纳尔多',2011,04,40); insert into tbl_sellers_info values ('罗纳尔多',2011,05,50); insert into tbl_sellers_info values ('德哥',2010,01,123456); insert into tbl_sellers_info values ('德哥',2010,02,234567); insert into tbl_sellers_info values ('德哥',2010,03,345678); insert into tbl_sellers_info values ('德哥',2010,04,345678); insert into tbl_sellers_info values ('德哥',2010,05,567890); insert into tbl_sellers_info values ('德哥',2010,06,123456); insert into tbl_sellers_info values ('德哥',2010,07,234567); insert into tbl_sellers_info values ('德哥',2010,08,345678); insert into tbl_sellers_info values ('德哥',2010,09,345678); insert into tbl_sellers_info values ('德哥',2010,10,567890); insert into tbl_sellers_info values ('德哥',2010,11,123456); insert into tbl_sellers_info values ('德哥',2010,12,234567); insert into tbl_sellers_info values ('贝克汉姆',2010,11,12); insert into tbl_sellers_info values ('贝克汉姆',2010,12,23); insert into tbl_sellers_info select * from tbl_sellers_info;

postgres=# select * from tbl_sellers_info ; seller | se_year | se_month | se_amount ----------+---------+----------+----------- 德哥 | 2011 | 1 | 123456 德哥 | 2011 | 2 | 234567 德哥 | 2011 | 3 | 345678 德哥 | 2011 | 4 | 345678 德哥 | 2011 | 5 | 567890 贝克汉姆 | 2011 | 1 | 12 贝克汉姆 | 2011 | 2 | 23 贝克汉姆 | 2011 | 3 | 34 贝克汉姆 | 2011 | 4 | 45 贝克汉姆 | 2011 | 5 | 56 卡洛斯 | 2011 | 3 | 12 卡洛斯 | 2011 | 4 | 45 卡洛斯 | 2011 | 5 | 56 罗纳尔多 | 2011 | 2 | 20 罗纳尔多 | 2011 | 3 | 30 罗纳尔多 | 2011 | 4 | 40 罗纳尔多 | 2011 | 5 | 50 德哥 | 2010 | 1 | 123456 德哥 | 2010 | 2 | 234567 德哥 | 2010 | 3 | 345678 德哥 | 2010 | 4 | 345678 德哥 | 2010 | 5 | 567890 德哥 | 2010 | 6 | 123456 德哥 | 2010 | 7 | 234567 德哥 | 2010 | 8 | 345678 德哥 | 2010 | 9 | 345678 德哥 | 2010 | 10 | 567890 德哥 | 2010 | 11 | 123456 德哥 | 2010 | 12 | 234567 贝克汉姆 | 2010 | 11 | 12 贝克汉姆 | 2010 | 12 | 23 德哥 | 2011 | 1 | 123456 德哥 | 2011 | 2 | 234567 德哥 | 2011 | 3 | 345678 德哥 | 2011 | 4 | 345678 德哥 | 2011 | 5 | 567890 贝克汉姆 | 2011 | 1 | 12 贝克汉姆 | 2011 | 2 | 23 贝克汉姆 | 2011 | 3 | 34 贝克汉姆 | 2011 | 4 | 45 贝克汉姆 | 2011 | 5 | 56 卡洛斯 | 2011 | 3 | 12 卡洛斯 | 2011 | 4 | 45 卡洛斯 | 2011 | 5 | 56 罗纳尔多 | 2011 | 2 | 20 罗纳尔多 | 2011 | 3 | 30 罗纳尔多 | 2011 | 4 | 40 罗纳尔多 | 2011 | 5 | 50 德哥 | 2010 | 1 | 123456 德哥 | 2010 | 2 | 234567 德哥 | 2010 | 3 | 345678 德哥 | 2010 | 4 | 345678 德哥 | 2010 | 5 | 567890 德哥 | 2010 | 6 | 123456 德哥 | 2010 | 7 | 234567 德哥 | 2010 | 8 | 345678 德哥 | 2010 | 9 | 345678 德哥 | 2010 | 10 | 567890 德哥 | 2010 | 11 | 123456 德哥 | 2010 | 12 | 234567 贝克汉姆 | 2010 | 11 | 12 贝克汉姆 | 2010 | 12 | 23 (62 rows)

行列变换,用JSON,将需要GROUP BY的多个字段合并成1个。

select js->>'seller' as seller, js->>'se_year' as se_year, jan , feb , mar , apr , may , jun , jul , aug , sep , oct , nov , dec from crosstab( -- 这个是需要进行行列变换的源SQL , 数据源。 -- 排序字段为group by字段 ,最后一个字段为转换后的内容字段,导数第二个字段为行列变换的字段(内容为枚举,比如月份) -- (必须在下一个参数中提取出对应的所有枚举值) $$select jsonb_build_object('seller', seller, 'se_year', se_year) as js, se_month, sum(se_amount) from tbl_sellers_info group by 1,2 order by 1$$, -- 行列转换的行,有哪些值被提取出来作为列。 这个在这里代表的是月份,也就是se_month的值 -- 或(select * from (values('jan'),...('dec')) t(se_month)) 'select distinct se_month from tbl_sellers_info order by 1' ) as -- crosstab 输出格式 ( js jsonb, -- 第一个参数SQL内对应的order by对应的字段(1个或多个) Jan numeric, -- 第一个参数SQL内对应导数第二个字段的枚举值,(行转列) feb numeric, -- ...同上 mar numeric, apr numeric, may numeric, jun numeric, jul numeric, aug numeric, sep numeric, oct numeric, nov numeric, dec numeric ) order by 1,2;

结果

seller | se_year | jan | feb | mar | apr | may | jun | jul | aug | sep | oct | nov | dec ----------+---------+--------+--------+--------+--------+---------+--------+--------+--------+--------+---------+--------+-------- 卡洛斯 | 2011 | | | 24 | 90 | 112 | | | | | | | 德哥 | 2010 | 246912 | 469134 | 691356 | 691356 | 1135780 | 246912 | 469134 | 691356 | 691356 | 1135780 | 246912 | 469134 德哥 | 2011 | 246912 | 469134 | 691356 | 691356 | 1135780 | | | | | | | 罗纳尔多 | 2011 | | 40 | 60 | 80 | 100 | | | | | | | 贝克汉姆 | 2010 | | | | | | | | | | | 24 | 46 贝克汉姆 | 2011 | 24 | 46 | 68 | 90 | 112 | | | | | | | (6 rows)

PostgreSQL 列转行

例子,将以上行列转换的结果,再转换回去。

with a as ( -- A对应原始数据(即需要列转行的数据) select js->>'seller' as seller, js->>'se_year' as se_year, jan , feb , mar , apr , may , jun , jul , aug , sep , oct , nov , dec from crosstab( -- 这个是需要进行行列变换的源SQL , 数据源。 -- 排序字段为group by字段 ,最后一个字段为转换后的内容字段,导数第二个字段为行列变换的字段(内容为枚举,比如月份) -- (必须在下一个参数中提取出对应的所有枚举值) $$select jsonb_build_object('seller', seller, 'se_year', se_year) as js, se_month, sum(se_amount) from tbl_sellers_info group by 1,2 order by 1$$, -- 行列转换的行,有哪些值被提取出来作为列。 这个在这里代表的是月份,也就是se_month的值 -- 或(select * from (values('jan'),...('dec')) t(se_month)) 'select distinct se_month from tbl_sellers_info order by 1' ) as -- crosstab 输出格式 ( js jsonb, -- 第一个参数SQL内对应的order by对应的字段(1个或多个) Jan numeric, -- 第一个参数SQL内对应导数第二个字段的枚举值,(行转列) feb numeric, -- ...同上 mar numeric, apr numeric, may numeric, jun numeric, jul numeric, aug numeric, sep numeric, oct numeric, nov numeric, dec numeric ) order by 1,2 ) , -- b , 用jsonb把多列合并为一列,并使用jsonb_each展开。 b as (select seller, se_year, jsonb_each(row_to_json(a)::jsonb-'seller'::text-'se_year'::text) as rec from a) select seller, se_year, (b.rec).key as month, (b.rec).value as sum from b;

结果

seller | se_year | month | sum ----------+---------+-------+--------- 卡洛斯 | 2011 | apr | 90 卡洛斯 | 2011 | aug | null 卡洛斯 | 2011 | dec | null 卡洛斯 | 2011 | feb | null 卡洛斯 | 2011 | jan | null 卡洛斯 | 2011 | jul | null 卡洛斯 | 2011 | jun | null 卡洛斯 | 2011 | mar | 24 卡洛斯 | 2011 | may | 112 卡洛斯 | 2011 | nov | null 卡洛斯 | 2011 | oct | null 卡洛斯 | 2011 | sep | null 德哥 | 2010 | apr | 691356 德哥 | 2010 | aug | 691356 德哥 | 2010 | dec | 469134 德哥 | 2010 | feb | 469134 德哥 | 2010 | jan | 246912 德哥 | 2010 | jul | 469134 德哥 | 2010 | jun | 246912 德哥 | 2010 | mar | 691356 德哥 | 2010 | may | 1135780 德哥 | 2010 | nov | 246912 德哥 | 2010 | oct | 1135780 德哥 | 2010 | sep | 691356 德哥 | 2011 | apr | 691356 德哥 | 2011 | aug | null 德哥 | 2011 | dec | null 德哥 | 2011 | feb | 469134 德哥 | 2011 | jan | 246912 德哥 | 2011 | jul | null 德哥 | 2011 | jun | null 德哥 | 2011 | mar | 691356 德哥 | 2011 | may | 1135780 德哥 | 2011 | nov | null 德哥 | 2011 | oct | null 德哥 | 2011 | sep | null 罗纳尔多 | 2011 | apr | 80 罗纳尔多 | 2011 | aug | null 罗纳尔多 | 2011 | dec | null 罗纳尔多 | 2011 | feb | 40 罗纳尔多 | 2011 | jan | null 罗纳尔多 | 2011 | jul | null 罗纳尔多 | 2011 | jun | null 罗纳尔多 | 2011 | mar | 60 罗纳尔多 | 2011 | may | 100 罗纳尔多 | 2011 | nov | null 罗纳尔多 | 2011 | oct | null 罗纳尔多 | 2011 | sep | null 贝克汉姆 | 2010 | apr | null 贝克汉姆 | 2010 | aug | null 贝克汉姆 | 2010 | dec | 46 贝克汉姆 | 2010 | feb | null 贝克汉姆 | 2010 | jan | null 贝克汉姆 | 2010 | jul | null 贝克汉姆 | 2010 | jun | null 贝克汉姆 | 2010 | mar | null 贝克汉姆 | 2010 | may | null 贝克汉姆 | 2010 | nov | 24 贝克汉姆 | 2010 | oct | null 贝克汉姆 | 2010 | sep | null 贝克汉姆 | 2011 | apr | 90 贝克汉姆 | 2011 | aug | null 贝克汉姆 | 2011 | dec | null 贝克汉姆 | 2011 | feb | 46 贝克汉姆 | 2011 | jan | 24 贝克汉姆 | 2011 | jul | null 贝克汉姆 | 2011 | jun | null 贝克汉姆 | 2011 | mar | 68 贝克汉姆 | 2011 | may | 112 贝克汉姆 | 2011 | nov | null 贝克汉姆 | 2011 | oct | null 贝克汉姆 | 2011 | sep | null (72 rows)

参考

http://www.oracle.com/technetwork/cn/articles/11g-pivot-101924-zhs.html

http://www.cnblogs.com/zlja/archive/2011/12/10/2449515.html

http://www.dba-oracle.com/t_pivot_examples.htm

http://www.postgresonline.com/journal/archives/283-Unpivoting-data-in-PostgreSQL.html

https://modern-sql.com/use-case/pivot

https://www.postgresql.org/docs/devel/static/tablefunc.html

《行列变换 - Use tablefunc complete row & column cross display in PostgreSQL》

PostgreSQL 许愿链接

您的愿望将传达给PG kernel hacker、数据库厂商等, 帮助提高数据库产品质量和功能, 说不定下一个PG版本就有您提出的功能点. 针对非常好的提议,奖励限量版PG文化衫、纪念品、贴纸、PG热门书籍等,奖品丰富,快来许愿。开不开森.

9.9元购买3个月阿里云RDS PostgreSQL实例

PostgreSQL 解决方案集合

德哥 / digoal's github - 公益是一辈子的事.

digoal's wechat

文章转载自digoal,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论