Oracle 行列轉換的一道微軟面試題

昨天 fannairu 給的一個微軟面試題, 寫 SQL 來實現功能, 裏面涉及到行列轉換, 這算是 SQL 裏一個比較好玩的功能了, 特將解題的思路記錄下來. 面試

題目: sql

如今一個商品銷售表 sale , 表結構以下 數據結構

clip_image002

想要轉換成如下結構的數據 oracle

 

clip_image001[4]

請用 Decode 實現.[微軟公司 2004 年面試題] 函數

 

解題思路: 優化

1. 第一步: 數據準備 spa

經過 這裏 的 sql 語句建立 sale 表. 建立後的表數據以下 .net

clip_image001

2. 第二步: 年月拆分 code

能夠看到, 原有的數據結構裏年月是在一塊兒的, 即都是以 200001 來表示 2000年01月, 這裏要使用 oracle 內建函數 substr 來將其析取出年、月來 blog

執行 SQL

select substr(t.ym, 1, 4) year, substr(t.ym, 5, 2) month, sale_amt 
from sale t

得如下結果

clip_image002[7]

能夠看到, 其年份與月份已經被拆分開來, 下一步將使用這個結果作爲子查詢進行行列轉換.

 

3. 第三步: 行列轉換

經過行列轉換, 將以上結果轉成按年/月列出的形式, 行列轉換的要點在於: 使用 decode 得出須要獲得的每一列的數據, 每列都使用一個 decode.

執行SQL

select a.year year,
       decode(a.month, '01', a.sale_amt, 0) month01,
       decode(a.month, '02', a.sale_amt, 0) month02,
       decode(a.month, '03', a.sale_amt, 0) month03,
       decode(a.month, '04', a.sale_amt, 0) month04,
       decode(a.month, '05', a.sale_amt, 0) month05,
       decode(a.month, '06', a.sale_amt, 0) month06,
       decode(a.month, '07', a.sale_amt, 0) month07,
       decode(a.month, '08', a.sale_amt, 0) month08,
       decode(a.month, '09', a.sale_amt, 0) month09,
       decode(a.month, '10', a.sale_amt, 0) month10,
       decode(a.month, '11', a.sale_amt, 0) month11,
       decode(a.month, '12', a.sale_amt, 0) month12
  from (select substr(t.ym, 1, 4) year, substr(t.ym, 5, 2) month, sale_amt
          from sale t) a

得如下結果

clip_image002[9]

能夠看到, 這裏使用上一步的結果做爲子查詢, 將其按列進行 decode; 最終數據已經被按每一個不一樣的月份展開了, 可是其所在年份也相應擴展開了, 下一步須要作的就是使用 Group By 了.

 

4. 第四步: 最終結果

將上面的結果做爲子查詢, 使用 group by 將相同年份的數據合到一塊兒, 能夠看到, 除有真正有效的月份外, 其餘都是0, 因此可使用 SUM 函數將其進行 group by.

執行 SQL

select b.year,
       sum(month01) month01,
       sum(month02) month02,
       sum(month03) month03,
       sum(month04) month04,
       sum(month05) month05,
       sum(month06) month06,
       sum(month07) month07,
       sum(month08) month08,
       sum(month09) month09,
       sum(month10) month10,
       sum(month11) month11,
       sum(month12) month12
from 
(select a.year year,
       decode(a.month, '01', a.sale_amt, 0) month01,
       decode(a.month, '02', a.sale_amt, 0) month02,
       decode(a.month, '03', a.sale_amt, 0) month03,
       decode(a.month, '04', a.sale_amt, 0) month04,
       decode(a.month, '05', a.sale_amt, 0) month05,
       decode(a.month, '06', a.sale_amt, 0) month06,
       decode(a.month, '07', a.sale_amt, 0) month07,
       decode(a.month, '08', a.sale_amt, 0) month08,
       decode(a.month, '09', a.sale_amt, 0) month09,
       decode(a.month, '10', a.sale_amt, 0) month10,
       decode(a.month, '11', a.sale_amt, 0) month11,
       decode(a.month, '12', a.sale_amt, 0) month12
  from (select substr(t.ym, 1, 4) year, substr(t.ym, 5, 2) month, sale_amt
          from sale t) a) b

group by b.year

得如下結果

clip_image002[13]

clip_image002[15]

這能夠看到, 已經能夠得出正確結果了, 可是這樣一共要進行兩個子查詢, 而後再 sum, 代碼寫的比較難看; 有沒有更爲優化的辦法呢?

 

5. 第五步: SQL 優化

(1). 第二步中的子查詢只是爲了拆分出年和月, 因此能夠合併到第二個子查詢裏

優化後 SQL 爲

select b.year,
       sum(month01) month01,
       sum(month02) month02,
       sum(month03) month03,
       sum(month04) month04,
       sum(month05) month05,
       sum(month06) month06,
       sum(month07) month07,
       sum(month08) month08,
       sum(month09) month09,
       sum(month10) month10,
       sum(month11) month11,
       sum(month12) month12
  from (select substr(t.ym, 1, 4) year,
               decode(substr(t.ym, 5, 2), '01', t.sale_amt, 0) month01,
               decode(substr(t.ym, 5, 2), '02', t.sale_amt, 0) month02,
               decode(substr(t.ym, 5, 2), '03', t.sale_amt, 0) month03,
               decode(substr(t.ym, 5, 2), '04', t.sale_amt, 0) month04,
               decode(substr(t.ym, 5, 2), '05', t.sale_amt, 0) month05,
               decode(substr(t.ym, 5, 2), '06', t.sale_amt, 0) month06,
               decode(substr(t.ym, 5, 2), '07', t.sale_amt, 0) month07,
               decode(substr(t.ym, 5, 2), '08', t.sale_amt, 0) month08,
               decode(substr(t.ym, 5, 2), '09', t.sale_amt, 0) month09,
               decode(substr(t.ym, 5, 2), '10', t.sale_amt, 0) month10,
               decode(substr(t.ym, 5, 2), '11', t.sale_amt, 0) month11,
               decode(substr(t.ym, 5, 2), '12', t.sale_amt, 0) month12
         from sale t) b

 group by b.year

執行結果與最終結果一致, 再也不貼出.

 

(2). 很明顯, 上面的 SQL 代碼, 最外層的 SUM 能夠直接寫到裏層, 即在 decode 完後直接進行 SUM 操做.

優化後 SQL 爲

select substr(t.ym, 1, 4) year,
       sum(decode(substr(t.ym, 5, 2), '01', t.sale_amt, 0)) month01,
       sum(decode(substr(t.ym, 5, 2), '02', t.sale_amt, 0)) month02,
       sum(decode(substr(t.ym, 5, 2), '03', t.sale_amt, 0)) month03,
       sum(decode(substr(t.ym, 5, 2), '04', t.sale_amt, 0)) month04,
       sum(decode(substr(t.ym, 5, 2), '05', t.sale_amt, 0)) month05,
       sum(decode(substr(t.ym, 5, 2), '06', t.sale_amt, 0)) month06,
       sum(decode(substr(t.ym, 5, 2), '07', t.sale_amt, 0)) month07,
       sum(decode(substr(t.ym, 5, 2), '08', t.sale_amt, 0)) month08,
       sum(decode(substr(t.ym, 5, 2), '09', t.sale_amt, 0)) month09,
       sum(decode(substr(t.ym, 5, 2), '10', t.sale_amt, 0)) month10,
       sum(decode(substr(t.ym, 5, 2), '11', t.sale_amt, 0)) month11,
       sum(decode(substr(t.ym, 5, 2), '12', t.sale_amt, 0)) month12
  from sale t
 group by substr(t.ym, 1, 4)

執行結果與最終結果一致, 再也不貼出.

這裏要注意的是, group by 的地方須要寫  group by substr(t.ym, 1, 4) 而不能寫成 group by year, 由於 別名的計算在 group by 後面(?).

 

6. 總結

能夠看到, 最終優化後的代碼是比較簡潔明瞭的, 但其可讀性並很差, 優化後的代碼也會更高效(少了兩層子查詢); 在 SQL 不夠熟練的時候, 仍是須要從 1-4 進行逐步推導出最後結果的.

若是是列轉行,就直接每一列都查出來,而後使用 UNION 就能夠了。

環境: Windows7 + Oracle 10g + PL/SQL Developer 9

相關文章
相關標籤/搜索