1. 程式人生 > >Oracle中,用一條Sql實現任意的行轉列拼接(不是decode)

Oracle中,用一條Sql實現任意的行轉列拼接(不是decode)

說明一下測試環境:Oracle9i,有朋友說10g上測試結果不正確,本人沒有條件,所以無法進行測試

表結構和資料如下(表名Test):

NO VALUE  NAME
1       a       測試1
1       b       測試2
1       c       測試3
1       d       測試4
2       e       測試5
4       f        測試6
4       g       測試7

Sql語句:
select No,
       ltrim(max(sys_connect_by_path(Value, ';')), ';') as Value,
       ltrim(max(sys_connect_by_path(Name, ';')), ';') as Name
  from (select No,
               Value,
               Name,
               rnFirst,
               lead(rnFirst) over(partition by No order by rnFirst) rnNext
          from (select a.No,
                       a.Value,
                       a.Name,
                       row_number() over(order by a.No, a.Value desc) rnFirst
                  from Test a) tmpTable1) tmpTable2
 start with rnNext is null
connect by rnNext = prior rnFirst
 group by No;
 


檢索結果如下:

NO VALUE    NAME
1    a;b;c;d   測試1;測試2;測試3;測試4
2    e            測試5
4    f;g          測試6;測試7

簡單解釋一下那個Sql吧:
1、最內層的Sql(即表tmpTable1),按No和Value排序,並列出行號:
select a.No,
       a.Value,
       a.Name,
       row_number() over(order by a.No, a.Value desc) rnFirst
  from Test a
該語句結果如下:
NO VALUE NAME RNFIRST
1     d       測試4     1
1     c       測試3     2
1     b       測試2     3
1     a       測試1     4
2     e       測試5     5
4     g       測試7     6
4     f       測試6     7


2、外層的Sql(即表tmpTable2),根據No分割槽,取出當前行對應的下一條記錄的行號欄位:
select No,
       Value,
       Name,
       rnFirst,
       lead(rnFirst) over(partition by No order by rnFirst) rnNext
  from (這裡是tmpTable1的SQL) tmpTable1

lead(rnFirst):取得下一行記錄的rnFirst欄位
over(partition by No order by rnFirst) 按rnFirst排序,並按No分割槽,分割槽就是如果下一行的No欄位與當前行的No欄位不相等時,不取下一行記錄顯示
該語句結果如下:
NO VALUE NAME RNFIRST RNNEXT
1     d        測試4     1         2
1     c        測試3      2         3
1     b        測試2     3         4
1     a        測試1     4         NULL
2     e        測試5     5         NULL
4     g        測試7     6         7
4     f         測試6     7         NULL


3、最後就是最外層的sys_connect_by_path函式與start遞迴了
sys_connect_by_path(Value, ';')
 start with rnNext is null
connect by rnNext = prior rnFirst
這個大概意思就是從rnNext為null的那條記錄開始,遞迴查詢,
如果前一記錄的rnFirst欄位等於當前記錄的rnNext欄位,就把2條記錄的Value用分號連線起來,
大家可以先試試下面這個沒有Max和Group的Sql:
select No,
       sys_connect_by_path(Value, ';') as Value,
       sys_connect_by_path(Name, ';') as Name
  from (select No,
               Value,
               Name,
               rnFirst,
               lead(rnFirst) over(partition by No order by rnFirst) rnNext
          from (select a.No,
                       a.Value,
                       a.Name,
                       row_number() over(order by a.No, a.Value desc) rnFirst
                  from Test a) tmpTable1) tmpTable2
 start with rnNext is null
connect by rnNext = prior rnFirst

結果是:
NO VALUE       NAME
1     ;a            ;測試1
1     ;a;b         ;測試1;測試2
1     ;a;b;c     ;測試1;測試2;測試3
1     ;a;b;c;d  ;測試1;測試2;測試3;測試4
2     ;e            ;測試5
4     ;f             ;測試6
4     ;f;g          ;測試6;測試7

可以看到,每個No的最後一條記錄就是我們要的了
所以在sys_connect_by_path外面套一個Max,再加個Group by No,得到的結果就是行轉列的結果了
最後再加一個Ltrim,去掉最前面的那個分號,完成。

你看明白了嗎?