天天看點

PostgreSQL Oracle 相容性 - 行列變換(pivot unpivot) (tablefunc, crosstab)

标簽

PostgreSQL , Oracle , 行列變換 , pivot , unpivot , tablefunc , crosstab , json , jsonb

https://github.com/digoal/blog/blob/master/201807/20180714_02.md#%E8%83%8C%E6%99%AF 背景

行列轉換是OLAP應用場景中,對資料透視常用的SQL之一。

https://github.com/digoal/blog/blob/master/201807/20180714_02.md#oracle-pivot-%E8%A1%8C%E8%BD%AC%E5%88%97 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.    
           

https://github.com/digoal/blog/blob/master/201807/20180714_02.md#oracle-unpivot-%E5%88%97%E8%BD%AC%E8%A1%8C 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    
           

https://github.com/digoal/blog/blob/master/201807/20180714_02.md#postgresql-%E8%A1%8C%E8%BD%AC%E5%88%97 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)    
           

https://github.com/digoal/blog/blob/master/201807/20180714_02.md#postgresql-%E5%88%97%E8%BD%AC%E8%A1%8C 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)    
           

https://github.com/digoal/blog/blob/master/201807/20180714_02.md#%E5%8F%82%E8%80%83 參考

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 《行列變換 - Use tablefunc complete row & column cross display in PostgreSQL》

繼續閱讀