2020年7月5日 星期日

資料反轉置-UNPIVOT(Multiple -columns)

先前資料反轉置(UNPIVOT)文章提到資料反轉置概念並介紹UNPIVOT指令用法,另一篇資料反轉置(UNPIVOT)-應用CROSS APPLY則是透過CROSS APPLY作法,前述案例一種屬性資料內容例進行反轉置,以下將介紹兩種資料內容之作法。將以下述學生緊急聯絡人為例,每名學生將定義兩位緊急聯絡人(姓名及電話,常見有另一關係』欄位,因版面配置省略)
原始資料

希望呈現
STUDENT
CONTACT01
PHONE01
CONTACT02
PHONE02
A
A
0900000001
A
0900000002
B
B
0911000002
B
0911000001
C
C
0922000002
C祖父
0922000001
: 每名學生將設定兩位緊急聯絡人
(姓名及電話兩種資料內容)


STUDENT
#
CONT_PSN
PHONE
A
1
A
0900000001
A
2
A
0900000002
B
1
B
0911000002
B
2
B
0911000001
C
1
C
0922000002
C
2
C祖父
0922000001


方法1: 應用笛卡兒積(Cartesian product
應用笛卡兒積(Cartesian product)方法,首先將資料筆數轉置欄位數量(緊急聯絡人為2)倍數成長,由於僅2組,ORACLE直接使用CSV以產生2筆資料列(Row),將第一筆(COLUMN_VALUE=1資料定義並反轉置第一緊急聯絡人,第二筆則為第二緊急聯絡人;MSSQL則是採用master.dbo.spt_values可得0~2047數值序列,將使用01兩筆為反轉置對應用。SQL如下。
ORACLE
MSSQL
SELECT P.STUDENT
     , COLUMN_VALUE "#"
     , CASE COLUMN_VALUE
WHEN '1' THEN Contact01
WHEN '2' THEN Contact02
END Cont_Psn
     , CASE COLUMN_VALUE
WHEN '1' THEN Phone01  
WHEN '2' THEN Phone02  
END Phone
FROM Phone P
     , TABLE(SYS.DBMS_DEBUG_VC2COLL( 1, 2 ))
SELECT P.STUDENT
     , number+1 "#"
     , CASE number
            WHEN 0 THEN Contact01
            WHEN 1 THEN Contact02
            END Cont_Psn
      , CASE number
            WHEN 0 THEN Phone01  
            WHEN 1 THEN Phone02  
        END Phone
FROM Phone P
     , master.dbo.spt_values N
WHERE 1=1
      AND N.type ='P'
      AND N.number<2

方法2: UNPIVOT指令
UNPIVOT為資料庫所提供之反轉置指令,對於一組資料內容轉時相當方便,先前資料反轉置(UNPIVOT)案例中,除轉置欄位資料內容外,原資料表的欄位名稱為亦為重要參考屬性需一併轉入,本範例中資料內容(聯絡人、電話兩組轉置內容)才是重點,欄位名稱僅是提供優先順序,以及連結(JOIN)兩組轉置內容上使用,不然資料會被擴展2(具有兩組轉置內容)SQL如下。
ORACLE
MSSQL
SELECT STUDENT
   , ROW_NUMBER() OVER(PARTITION BY STUD_NAME
               ORDER BY Cont_Col) "#"
   , Cont_Psn   --1a.聯絡人姓名
   --, Cont_Col --1b.欄位名稱
   , Phone       --2a.聯絡電話
   --, Phone_Col--2b.欄位名稱
FROM Phone P
     UNPIVOT
     (
     Cont_Psn      --1a.聯絡人姓名
     FOR Cont_Col --1b.欄位名稱(屬性)
         IN (Contact01, Contact02)
     ) CON  
     UNPIVOT
     (
     Phone          --2a.聯絡電話
     FOR Phone_Col --2b.欄位名稱(屬性)
         IN (Phone01, Phone02)
     ) PH 
WHERE 1=1
 AND SUBSTR(Cont_Col, -2)   --1b.Contact[01]
   = SUBSTR(Phone_Col, -2) --2b.Phone[01]
SELECT STUDENT
   , ROW_NUMBER() OVER(PARTITION BY STUD_NAME
               ORDER BY Cont_Col) "#"
   , Cont_Psn   --1a.聯絡人姓名
   , Cont_Col   --1b.欄位名稱
   , Phone       --2a.聯絡電話
   --, Phone_Col --2b.欄位名稱
FROM Phone P
     UNPIVOT
     (
     Cont_Psn     --1a.聯絡人姓名
     FOR Cont_Col --1b.欄位名稱(屬性)
         IN (Contact01, Contact02)
     ) CON  
     UNPIVOT
     (
     Phone          --2a.聯絡電話
     FOR Phone_Col --2b.欄位名稱(屬性)
         IN (Phone01, Phone02)
     ) PH 
WHERE 1=1
 AND RIGHT(Cont_Col, 2)   --1b.Contact[01]
   = RIGHT(Phone_Col, 2)  --2b.Phone[01]

方法3: CROSS APPLY
相對上述兩種方法,CROSS APPLY作法方法相對簡單許多,詳細用法可參考如先前《資料反轉置(UNPIVOT)-應用CROSS APPLY》文章所探討,SQL如下。MSSQL2005開始提供此方法,並可針對CROSS APPLY引用資料集以別名(alias)命名及指定其欄位名稱;ORACLE 12C起提供CROSS APPLY方法,但未援別名及欄位名稱定義。SQL如下。
ORACLE
MSSQL
SELECT P.STUDENT
     , SEQ "#"
     , Cont_Psn
     , Phone
FROM Phone P
     CROSS APPLY
     (
      SELECT 1 SEQ
           , Contact01 Cont_Psn
           , Phone01 Phone
      FROM DUAL
      UNION ALL
      SELECT 2, Contact02, Phone02
      FROM DUAL
     )  --不得使用別名及定義欄位名稱
SELECT P.STUDENT
     , SEQ "#"
     , Cont_Psn
     , Phone
FROM Phone P
     CROSS APPLY
     (VALUES (1, Contact01, Phone01)
           , (2, Contact01, Phone01)
     ) CON (SEQ, Cont_Psn, Phone)


以上列出三種方法,由內容及複雜度來看,UNPIVOT相對複雜度較高(較不易理解)CROSS APPLY相對直覺且簡單,笛卡兒積(Cartesian product)亦為不錯方法。對於解決問題方的法選擇上,宜海納百川切尤忌墨守成規,各種方法都有其適用性,望能多加思考找出較為適切之作法。

沒有留言:

張貼留言