Oracle 行列转换的一道微软面试题

北战南征 提交于 2019-11-30 06:11:25

昨天 fannairu 给的一个微软面试题, 写 SQL 来实现功能, 里面涉及到行列转换, 这算是 SQL 里一个比较好玩的功能了, 特将解题的思路记录下来.

题目:

现在一个商品销售表 sale , 表结构如下

clip_image002

想要转换成以下结构的数据

 

clip_image001[4]

请用 Decode 实现.[微软公司 2004 年面试题]

 

解题思路:

1. 第一步: 数据准备

通过 这里 的 sql 语句创建 sale 表. 创建后的表数据如下

clip_image001

2. 第二步: 年月拆分

可以看到, 原有的数据结构里年月是在一起的, 即都是以 200001 来表示 2000年01月, 这里要使用 oracle 内建函数 substr 来将其析取出年、月来

执行 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

 

 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!