Oracle 行转列和列转行

使用场景:在生成报表的时候,很多时候对某列取值固定或者有限的几个值时,进行转列分析。如下:
这里写图片描述
facility列取值只有四种,每种一列,遇到相同facility求和。如下图:
这里写图片描述
实现方式:
1. DECODE
利用DECODE函数、聚合函数SUM、GROUP BY分组实现。

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;  

该方式简单直观,当然每列具体要标示出来,所以只能处理固定种类的,不易程序后期扩展。
2. case
case函数的写法相对比较简洁,如下:

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 ASC;  

这里写图片描述
值得注意的是 case函数只返回第一个符合条件的值,剩下的Case部分将会被自动忽略。
3. PIVOT
直接使用Oracle 关键字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'));  

改方式更加的简便,最重要的是,分列条件是动态的,不需要考虑取值的可能性,利于扩展。不过,PIVOT是Oracle 11g后才出现的,所以使用时要注意环境。
另外,列名是动态的,所以在select中写列名报错,这点不知道怎么解决,有高手的话帮忙看下。
4. 多列求和
转列之后,很多时候需要对同一行中某些列求和。

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,ZHA01,ZHA02,ZHA03,ZHA04,ZHA01+ZHA02+ZHA03 +ZHA04 as TOTAL from (  
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);  

效果如下:
这里写图片描述
5. 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;  

效果如下:
这里写图片描述
6. 去重
例子中,(wb_id, reply, addre) 作为表的unique key, 如果(wb_id, reply, addre) 相同,保留字段TEU 最大值的记录。删除其他记录。

WITH tt AS (  
  SELECT 'DOM1' wb_id, 'ZHA01' reply ,  'ZHA01' addre ,4000 TEU FROM dual UNION ALL        
  SELECT 'DOM1' wb_id, 'ZHA01' reply ,  'ZHA01' addre ,5000 TEU FROM dual UNION ALL  --- (wb_id, reply, addre)与上条记录相同,不过 TEU 更大,保留。  
  SELECT 'DOM1' wb_id, 'ZHA02' reply ,  'ZHA01' addre ,9000 TEU FROM dual UNION ALL       
  SELECT 'DOM2' wb_id, 'ZHA01' reply ,  'ZHA01' addre ,9000 TEU FROM dual UNION ALL      
  SELECT 'DOM2' wb_id, 'ZHA04' reply ,  'ZHA01' addre ,4000 TEU FROM dual UNION ALL      
  SELECT 'DOM2' wb_id, 'ZHA01' reply ,  'ZHA01' addre ,6500 TEU FROM dual           --- (wb_id, reply, addre)与第四条记录相同,不过 TEU 小,删除。  
)  

-- 选出待删除的数据集合  
SELECT *  FROM tt  
WHERE (wb_id, reply,addre) IN (SELECT wb_id, reply, addre FROM tt GROUP BY wb_id, reply, addre HAVING count(1) > 1)  
AND (wb_id, reply,reply, TEU) NOT IN (SELECT wb_id, reply, addre, max(TEU) FROM tt group by wb_id, reply, addre)  
;  

7.列转行
上面说到用 PRIVOT 进行行转列,现在看下 UNPRIVOT 操作。从名字上看,就是 PRIVOT 加前缀 UN,意义不言而喻,就是进行列转行。先看例子。

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 * FROM CO_ORDER2   
SELECT Customer, offcs, facility FROM CO_ORDER2 UNPIVOT (offcs FOR facility IN ( ZHA02, ZHA03, ZHA04) ); 

首先数据原始结构如下:
这里写图片描述
而利用 UNPRIVOT 处理后,结果如下:
这里写图片描述
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,   ZHA02, 'office1' ZHA03, 'office1' ZHA04 FROM dual         
)  
--SELECT * FROM CO_ORDER2   
SELECT Customer, offcs, facility FROM CO_ORDER2 UNPIVOT (offcs FOR facility IN ( ZHA02, ZHA03, ZHA04) );  

此时下面只产生了 10 行数据。
这里写图片描述
在上面的 数据中,我们 给其中两个格子里面,一个为 空字符串,一个是 null。最终这两条数据没出现在结果集中。

猜你喜欢

转载自blog.csdn.net/Gary_888/article/details/79947854