Oracle行轉列-列轉行各種方式wm_concat函數、LISTAGG函數、PIVOT函數、UNPIVOT函數、非數字,以及自動動態獲取要轉換的列字段名(超詳細)

1、上來先看下數據以及實現結果:

行轉列(decode方式):

WITH CO_ORDER AS(    
  SELECT 'DOM1' CUSTOMER, 'ZHA01' FACILITY , 4000 TEU FROM DUAL UNION ALL    
  SELECT 'DOM1' CUSTOMER, 'ZHA01' FACILITY , 5000 TEU FROM DUAL UNION ALL    
  SELECT 'DOM1' CUSTOMER, 'ZHA02' FACILITY , 9000 TEU FROM DUAL UNION ALL   
  SELECT 'DOM1' CUSTOMER, 'ZHA03' FACILITY , 9000 TEU FROM DUAL UNION ALL  
  SELECT 'DOM1' CUSTOMER, 'ZHA04' FACILITY , 4000 TEU FROM DUAL UNION ALL  
  SELECT 'DOM2' CUSTOMER, 'ZHA01' FACILITY , 6500 TEU FROM DUAL UNION ALL    
  SELECT 'DOM2' CUSTOMER, 'ZHA02' FACILITY , 6000 TEU FROM DUAL UNION ALL    
  SELECT 'DOM2' CUSTOMER, 'ZHA03' FACILITY , 5000 TEU FROM DUAL UNION ALL    
  SELECT 'DOM2' CUSTOMER, 'ZHA04' FACILITY , 3000 TEU FROM DUAL     
)
SELECT T.CUSTOMER, 
	SUM(DECODE(T.FACILITY, 'ZHA01', T.TEU)) AS ZHA01,
    SUM(DECODE(T.FACILITY, 'ZHA02', T.TEU)) AS ZHA02,
    SUM(DECODE(T.FACILITY, 'ZHA03', T.TEU)) AS ZHA03,
    SUM(DECODE(T.FACILITY, 'ZHA04', T.TEU)) AS ZHA04
FROM CO_ORDER T
GROUP BY T.CUSTOMER;

行轉列(case when方式):

WITH CO_ORDER AS(      
  SELECT 'DOM1' CUSTOMER, 'ZHA01' FACILITY , 4000 TEU FROM DUAL UNION ALL      
  SELECT 'DOM1' CUSTOMER, 'ZHA01' FACILITY , 5000 TEU FROM DUAL UNION ALL      
  SELECT 'DOM1' CUSTOMER, 'ZHA02' FACILITY , 9000 TEU FROM DUAL UNION ALL     
  SELECT 'DOM1' CUSTOMER, 'ZHA03' FACILITY , 9000 TEU FROM DUAL UNION ALL    
  SELECT 'DOM1' CUSTOMER, 'ZHA04' FACILITY , 4000 TEU FROM DUAL UNION ALL    
  SELECT 'DOM2' CUSTOMER, 'ZHA01' FACILITY , 6500 TEU FROM DUAL UNION ALL      
  SELECT 'DOM2' CUSTOMER, 'ZHA02' FACILITY , 6000 TEU FROM DUAL UNION ALL      
  SELECT 'DOM2' CUSTOMER, 'ZHA03' FACILITY , 5000 TEU FROM DUAL UNION ALL      
  SELECT 'DOM2' CUSTOMER, 'ZHA04' FACILITY , 3000 TEU FROM DUAL       
) 
SELECT CUSTOMER,
    SUM (CASE WHEN FACILITY = 'ZHA01' THEN TEU ELSE 0 END) AS ZHA01, 
    SUM (CASE WHEN FACILITY = 'ZHA02' THEN TEU ELSE 0 END) AS ZHA02,
    SUM (CASE WHEN FACILITY = 'ZHA03' THEN TEU ELSE 0 END) AS ZHA03,
    SUM (CASE WHEN FACILITY = 'ZHA04' THEN TEU ELSE 0 END) AS ZHA04
FROM CO_ORDER GROUP BY CUSTOMER ORDER BY CUSTOMER;

行轉列(PIVOT函數方式:)

WITH CO_ORDER AS(    
  SELECT 'DOM1' CUSTOMER, 'ZHA01' FACILITY , 4000 TEU FROM DUAL UNION ALL    
  SELECT 'DOM1' CUSTOMER, 'ZHA01' FACILITY , 5000 TEU FROM DUAL UNION ALL    
  SELECT 'DOM1' CUSTOMER, 'ZHA02' FACILITY , 9000 TEU FROM DUAL UNION ALL   
  SELECT 'DOM1' CUSTOMER, 'ZHA03' FACILITY , 9000 TEU FROM DUAL UNION ALL  
  SELECT 'DOM1' CUSTOMER, 'ZHA04' FACILITY , 4000 TEU FROM DUAL UNION ALL  
  SELECT 'DOM2' CUSTOMER, 'ZHA01' FACILITY , 6500 TEU FROM DUAL UNION ALL    
  SELECT 'DOM2' CUSTOMER, 'ZHA02' FACILITY , 6000 TEU FROM DUAL UNION ALL    
  SELECT 'DOM2' CUSTOMER, 'ZHA03' FACILITY , 5000 TEU FROM DUAL UNION ALL    
  SELECT 'DOM2' CUSTOMER, 'ZHA04' FACILITY , 3000 TEU FROM DUAL     
)
SELECT * FROM (
	SELECT T.CUSTOMER, T.FACILITY, SUM(TEU) EU FROM CO_ORDER T GROUP BY T.CUSTOMER, T.FACILITY) T 
PIVOT(SUM(T.EU) FOR FACILITY IN ('ZHA01', 'ZHA02', 'ZHA03', 'ZHA04'));

 行轉列(WM_CONCAT函數方式:)

WITH CO_ORDER AS(    
  SELECT 'DOM1' CUSTOMER, 'ZHA01' FACILITY , 4000 TEU FROM DUAL UNION ALL    
  SELECT 'DOM1' CUSTOMER, 'ZHA01' FACILITY , 5000 TEU FROM DUAL UNION ALL    
  SELECT 'DOM1' CUSTOMER, 'ZHA02' FACILITY , 9000 TEU FROM DUAL UNION ALL   
  SELECT 'DOM1' CUSTOMER, 'ZHA03' FACILITY , 9000 TEU FROM DUAL UNION ALL  
  SELECT 'DOM1' CUSTOMER, 'ZHA04' FACILITY , 4000 TEU FROM DUAL UNION ALL  
  SELECT 'DOM2' CUSTOMER, 'ZHA01' FACILITY , 6500 TEU FROM DUAL UNION ALL    
  SELECT 'DOM2' CUSTOMER, 'ZHA02' FACILITY , 6000 TEU FROM DUAL UNION ALL    
  SELECT 'DOM2' CUSTOMER, 'ZHA03' FACILITY , 5000 TEU FROM DUAL UNION ALL    
  SELECT 'DOM2' CUSTOMER, 'ZHA04' FACILITY , 3000 TEU FROM DUAL     
)
SELECT CUSTOMER,WM_CONCAT(FACILITY) ALL_FACILITY FROM CO_ORDER GROUP BY CUSTOMER;

列轉行(UNPIVOT函數方式:)

WITH CO_ORDER2 AS(      
  SELECT 'DOM1' CUSTOMER, 'OFFICE1'  ZHA02, 'OFFICE1' ZHA03, 'OFFICE2' ZHA04 FROM DUAL UNION ALL      
  SELECT 'DOM1' CUSTOMER, 'OFFICE2'  ZHA02, 'OFFICE3' ZHA03, 'OFFICE2' ZHA04 FROM DUAL UNION ALL        
  SELECT 'DOM2' CUSTOMER, 'OFFICE4'  ZHA02, 'OFFICE1' ZHA03, 'OFFICE2' ZHA04 FROM DUAL UNION ALL      
  SELECT 'DOM2' CUSTOMER, 'OFFICE3'  ZHA02, 'OFFICE1' ZHA03, 'OFFICE1' ZHA04 FROM DUAL       
)
SELECT CUSTOMER, OFFCS, FACILITY FROM CO_ORDER2 UNPIVOT (OFFCS FOR FACILITY IN ( ZHA02, ZHA03, ZHA04) );  

/*
UNPRIVOT 幫我們生成了兩個新列,一列用於存放 office1 之類的值,另一個新列存的是 這個數據來源於之前那個原始列名。
其實它做的很簡單,就是對每行原始數據的指定列的每個格子進行遍歷,生成一條條數據。將原有的結構進行打平。因此,
處理後數據的行數 = 原始數據行數  * 指定列數
因此上面 產生 了 12 條數據。
不過這也不是絕對的,當某個數據爲空是,最終生成的數據會被 UNPRIVOT 給刪除*/

WITH CO_ORDER2 AS(      
  SELECT 'DOM1' CUSTOMER, 'OFFICE1'  ZHA02, 'OFFICE1' ZHA03, 'OFFICE2' ZHA04 FROM DUAL UNION ALL      
  SELECT 'DOM1' CUSTOMER, 'OFFICE2'  ZHA02, ''        ZHA03, 'OFFICE2' ZHA04 FROM DUAL UNION ALL        
  SELECT 'DOM2' CUSTOMER, 'OFFICE4'  ZHA02, 'OFFICE1' ZHA03, 'OFFICE2' ZHA04 FROM DUAL UNION ALL      
  SELECT 'DOM2' CUSTOMER,  NULL      ZHA02, 'OFFICE1' ZHA03, 'OFFICE1' ZHA04 FROM DUAL       
)
SELECT CUSTOMER, OFFCS, FACILITY FROM CO_ORDER2 UNPIVOT (OFFCS FOR FACILITY IN ( ZHA02, ZHA03, ZHA04) ); 

--在上面的 數據中,我們 給其中兩個格子裏面,一個爲 空字符串,一個是 null。最終這兩條數據沒出現在結果集中

 

--以上轉自https://blog.csdn.net/hustzw07/article/details/51426935

 

----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

--行轉列

CREATE TABLE DEMO(ID NUMBER,NAME VARCHAR2(20),NUMS NUMBER);  ---- 創建表
INSERT INTO DEMO VALUES(1, '蘋果', 1000);
INSERT INTO DEMO VALUES(2, '蘋果', 2000);
INSERT INTO DEMO VALUES(3, '蘋果', 4000);
INSERT INTO DEMO VALUES(4, '橘子', 5000);
INSERT INTO DEMO VALUES(5, '橘子', 3000);
INSERT INTO DEMO VALUES(6, '葡萄', 3500);
INSERT INTO DEMO VALUES(7, '芒果', 4200);
INSERT INTO DEMO VALUES(8, '芒果', 5500);
commit;
SELECT * FROM (
	SELECT NAME, NUMS FROM DEMO) 
PIVOT (SUM(NUMS) FOR NAME IN ('蘋果' "蘋果", '橘子', '葡萄', '芒果'));

--pivot(聚合函數 for 列名 in(類型)) ,其中 in('') 中可以指定別名,當然也可以不使用pivot函數,等同於下列語句

SELECT * FROM 
	(SELECT SUM(NUMS) "蘋果" FROM DEMO WHERE NAME = '蘋果'),
    (SELECT SUM(NUMS) "橘子" FROM DEMO WHERE NAME = '橘子'),
    (SELECT SUM(NUMS) "葡萄" FROM DEMO WHERE NAME = '葡萄'),
    (SELECT SUM(NUMS) "芒果" FROM DEMO WHERE NAME = '芒果');

 

--列轉行

CREATE TABLE FRUIT(ID NUMBER,NAME VARCHAR2(20), Q1 NUMBER, Q2 NUMBER, Q3 NUMBER, Q4 NUMBER);
INSERT INTO FRUIT VALUES(1,'蘋果',1000,2000,3300,5000);
INSERT INTO FRUIT VALUES(2,'橘子',3000,3000,3200,1500);
INSERT INTO FRUIT VALUES(3,'香蕉',2500,3500,2200,2500);
INSERT INTO FRUIT VALUES(4,'葡萄',1500,2500,1200,3500);
commit;
SELECT ID , NAME, JIDU, XIAOSHOU 
FROM FRUIT 
UNPIVOT (XIAOSHOU FOR JIDU IN (Q1, Q2, Q3, Q4));

--同樣不使用unpivot也可以實現同樣的效果,只是sql語句會很長,而且執行速度效率也沒有前者高

SELECT ID, NAME ,'Q1' JIDU, (SELECT Q1 FROM FRUIT WHERE ID=F.ID) XIAOSHOU FROM FRUIT F
UNION all
SELECT ID, NAME ,'Q2' JIDU, (SELECT Q2 FROM FRUIT WHERE ID=F.ID) XIAOSHOU FROM FRUIT F
UNION all
SELECT ID, NAME ,'Q3' JIDU, (SELECT Q3 FROM FRUIT WHERE ID=F.ID) XIAOSHOU FROM FRUIT F
UNION all
SELECT ID, NAME ,'Q4' JIDU, (SELECT Q4 FROM FRUIT WHERE ID=F.ID) XIAOSHOU FROM FRUIT F

 

------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

--解釋一下wm_concat、listagg這兩個函數

CREATE TABLE TEST(ID NUMBER,NAME VARCHAR2(20));

INSERT INTO TEST VALUES(1,'A');
INSERT INTO TEST VALUES(1,'B');
INSERT INTO TEST VALUES(1,'C');
INSERT INTO TEST VALUES(2,'D');
INSERT INTO TEST VALUES(2,'E');
commit;

--wm_concat函數,這個神奇的函數wm_concat(列名),該函數可以把列值以","號分隔起來,並顯示成一行

select wm_concat(NAME) NAME from TEST;
select ID,wm_concat(NAME) NAME from TEST group by ID;

--再配合replace函數,可以做實際多種操作

select replace(wm_concat(NAME),',','|') from TEST;

--不用wm_concat函數想要達到同樣效果的語法(不推薦)

select 
	ID,
    max(decode(rn, 1, NAME, null)) ||
    max(decode(rn, 2, ',' || NAME, null)) ||
    max(decode(rn, 3, ',' || NAME, null)) str
from (
	select 
		ID,
        NAME,
        row_number() over(partition by ID order by NAME) as rn
    from TEST) t
group by ID
order by 1;
select 
	ID, str
from (
	select 
		ID,
        row_number() over(partition by ID order by NAME) as rn,
        NAME || lead(',' || NAME, 1) over(partition by ID order by NAME) ||
        lead(',' || NAME, 2) over(partition by ID order by NAME) || 
        lead(',' || NAME, 3) over(partition by ID order by NAME) as str
    from TEST)
where rn = 1
order by 1;
SELECT 
	ID, SUBSTR(STR, 2) STR
FROM TEST 
	MODEL RETURN UPDATED ROWS PARTITION BY(ID) DIMENSION BY(ROW_NUMBER() OVER(PARTITION BY ID ORDER BY NAME) AS RN) 
	MEASURES(CAST(NAME AS VARCHAR2(20)) AS STR)
	RULES UPSERT ITERATE(3) UNTIL(PRESENTV(STR [ ITERATION_NUMBER + 2 ], 1, 0) = 0)
	(STR [ 0 ] = STR [ 0 ] || ',' || STR [ ITERATION_NUMBER + 1 ])
ORDER BY 1;
SELECT 
	T.ID ID, 
	MAX(SUBSTR(SYS_CONNECT_BY_PATH(T.NAME, ','), 2)) STR
FROM (
	SELECT ID, NAME, ROW_NUMBER() OVER(PARTITION BY ID ORDER BY NAME) RN FROM TEST) T
START WITH RN = 1
CONNECT BY RN = PRIOR RN + 1 AND ID = PRIOR ID
GROUP BY T.ID;

--上面這幾個本人表示難以理解使用

 

--或者使用LISTAGG函數呢
--最基礎的用法    LISTAGG(XXX,XXX) WITHIN GROUP( ORDER BY XXX)

select ID,listagg(NAME,',') within GROUP (order by NAME)  as NAMES from TEST group by ID;

--關於LISTAGG函數的基礎用法

with temp as(  
select 'China' nation ,'Guangzhou' city from dual union all  
select 'China' nation ,'Shanghai' city from dual union all  
select 'China' nation ,'Beijing' city from dual union all  
select 'USA' nation ,'New York' city from dual union all  
select 'USA' nation ,'Bostom' city from dual union all  
select 'Japan' nation ,'Tokyo' city from dual   
)  
select nation,listagg(city,',') within GROUP (order by city)  as Cities
from temp  
group by nation;

--關於LISTAGG函數的另一個用法

with temp as(  
select 500 population, 'China' nation ,'Guangzhou' city from dual union all  
select 1500 population, 'China' nation ,'Shanghai' city from dual union all  
select 500 population, 'China' nation ,'Beijing' city from dual union all  
select 1000 population, 'USA' nation ,'New York' city from dual union all  
select 500 population, 'USA' nation ,'Bostom' city from dual union all  
select 500 population, 'Japan' nation ,'Tokyo' city from dual   
)  
select population,  
nation,  
city,  
listagg(city,',') within GROUP (order by city) over (partition by nation) CITIES  
from temp;

--做個小拓展
--我要寫一個視圖,類似"create or replace view as select 字段1,...字段50 from tablename" ,基表有50多個字段,要是靠手工寫太麻煩了,有沒有什麼簡便的方法? 當然有了,看我如果應用wm_concat來讓這個需求變簡單,以SCOTT下的員工表爲例(注意表名重複)

SELECT 
	'CREATE OR REPLACE VIEW AS SELECT ' || WM_CONCAT(COLUMN_NAME) || ' FROM EMP' SQLSTR
FROM ALL_TAB_COLUMNS
WHERE TABLE_NAME = 'EMP';

-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

--下面這幾個轉換裏面沒有數字

WITH TEMP AS(
	SELECT '四川省' NATION ,'成都市' CITY,'第一' RANKING FROM DUAL UNION ALL 
	SELECT '四川省' NATION ,'綿陽市' CITY,'第二' RANKING FROM DUAL UNION ALL 
	SELECT '四川省' NATION ,'德陽市' CITY,'第三' RANKING FROM DUAL UNION ALL 
	SELECT '四川省' NATION ,'宜賓市' CITY,'第四' RANKING FROM DUAL UNION ALL 
	SELECT '湖北省' NATION ,'武漢市' CITY,'第一' RANKING FROM DUAL UNION ALL 
	SELECT '湖北省' NATION ,'宜昌市' CITY,'第二' RANKING FROM DUAL UNION ALL 
	SELECT '湖北省' NATION ,'襄陽市' CITY,'第三' RANKING FROM DUAL 
)
SELECT * FROM (SELECT NATION,CITY,RANKING FROM TEMP) 
PIVOT (MAX(CITY) FOR RANKING IN ('第一' AS 第一,'第二' AS 第二,'第三' AS 第三,'第四' AS 第四));

 

WITH TEMP AS(
	SELECT '四川省' NATION ,'成都市' CITY,'第一' RANKING FROM DUAL UNION ALL 
	SELECT '四川省' NATION ,'綿陽市' CITY,'第二' RANKING FROM DUAL UNION ALL 
	SELECT '四川省' NATION ,'德陽市' CITY,'第三' RANKING FROM DUAL UNION ALL 
	SELECT '四川省' NATION ,'宜賓市' CITY,'第四' RANKING FROM DUAL UNION ALL 
	SELECT '湖北省' NATION ,'武漢市' CITY,'第一' RANKING FROM DUAL UNION ALL 
	SELECT '湖北省' NATION ,'宜昌市' CITY,'第二' RANKING FROM DUAL UNION ALL 
	SELECT '湖北省' NATION ,'襄陽市' CITY,'第三' RANKING FROM DUAL 
)
SELECT NATION,
	MAX(DECODE(RANKING, '第一', CITY, '')) AS 第一,
	MAX(DECODE(RANKING, '第二', CITY, '')) AS 第二,
	MAX(DECODE(RANKING, '第三', CITY, '')) AS 第三,
	MAX(DECODE(RANKING, '第四', CITY, '')) AS 第四
FROM TEMP GROUP BY NATION;

 

WITH TEMP AS(
	SELECT '四川省' NATION ,'成都市' 第一,'綿陽市' 第二,'德陽市' 第三,'宜賓市' 第四  FROM DUAL UNION ALL 
	SELECT '湖北省' NATION ,'武漢市' 第一,'宜昌市' 第二,'襄陽市' 第三,'' 第四   FROM DUAL 
)
SELECT NATION,NAME,TITLE FROM TEMP UNPIVOT (NAME FOR TITLE IN (第一,第二,第三,第四))

 

-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

--有時候可能需要行轉列的值,即shangPin字段的值的個數很多,或者是不確定個數,那 in () 裏面的部分就不好去寫死,然後,Oracle的pivot其實也是提供了一個轉出動態列的功能,不過轉出來的是xml格式的數據,下面我們通過存儲過程實現

 

--建表,插入模擬數據

--drop table SALESLIST;
create table SALESLIST(
    KEHU                varchar2(20),   --客戶
    SHANGPINID          number(8),      --商品Id
    SHANGPIN            varchar2(20),   --商品名稱
    SALESNUM            number(8)       --銷售數量
);

declare
	--談幾個客戶
	cursor lr_kh is 
	select regexp_substr('張三、李四、王五、趙六','[^、]+',1, level) KEHU from dual
	connect by level <= 4;
	   
	--進點貨
	cursor lr_sp is 
	select level SHANGPINID, regexp_substr('上衣、褲子、襪子、帽子','[^、]+',1, level) SHANGPIN from dual
	connect by level <= 4;
begin
--循環插入
	for v_kh in lr_kh loop
		for v_sp in lr_sp loop
			insert into SALESLIST select v_kh.KEHU, v_sp.SHANGPINID, v_sp.SHANGPIN, floor(dbms_random.value(10,50)) from dual;
		end loop;
	end loop;
	commit;
end;
/

 

--固定行轉列

select * from (
	select KEHU, SHANGPIN, SALESNUM from SALESLIST) pivot(max(SALESNUM) for SHANGPIN in ('上衣' as 上衣,'褲子' as 褲子,'襪子' as 襪子,'帽子' as 帽子)
  );

 

--動態行轉列

--創建存儲過程

create or replace procedure P_ROWSTOCOLS(
	as_sql        in varchar2 --源數據的查詢sql
    ,as_sql_cols  in varchar2 --動態轉換列的查詢sql,要求轉爲列的那列,字段名必須爲cols,支持排序
    ,as_aggCol    in varchar2 --對應pivot函數的 聚合函數
    ,as_changeCol in varchar2 --源數據中,要轉爲列的字段名
    ,as_viewName  in varchar2 --結果輸出的視圖名,執行完後查此視圖即可
	)
is
  ls_sql varchar2(4000);
  ls_in  varchar2(4000);
  
begin

  --拼接in的內容
	ls_sql := 'select listagg(''''''''||cols||'''''' "''||cols||''"'', '','')within group(order by rn) ' || 'from (select rownum rn, cols from (' || as_sql_cols || '))';
	execute immediate ls_sql
	into ls_in;
 
  --創建視圖
	ls_sql := 'create or replace view ' || as_viewName ||' as ' || 'select * from (' || as_sql || ') ' || 'pivot (' || as_aggCol || ' for ' || as_changeCol || ' in (' || ls_in || '))';
	execute immediate ls_sql;
end P_ROWSTOCOLS;
/

--執行存儲過程,創建行轉列之後的視圖

call P_ROWSTOCOLS(
	'select KEHU, SHANGPIN, SALESNUM from SALESLIST',
    'select distinct SHANGPINID, SHANGPIN cols from SALESLIST order by SHANGPINID',
    'max(SALESNUM)',
    'SHANGPIN',
    'SALES_ROWSTOCOLS');

--查詢視圖就是我們要的結果

select * from SALES_ROWSTOCOLS;

--以上動態獲取轉自https://blog.csdn.net/Huay_Li/article/details/82924443

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章