2013-8-20
1. SQL查詢表的行列轉換/小計/統計(with rollup,with cube,pivot解析)
在實際的項目開發中有很多項目都會有報表模塊,今天就通過一個小的SQL查詢統計來講解一下實際開發中比較常用的行列轉換/小計/統計等報表統計相關的常用知識點。
題目如下:
查詢sales 和stores表,得出1993年每個store每季度銷售數量及小計和總計,查詢出的結果如下
其中sales表的數據結構如下:
其中stores表的數據結構如下:
1.1 普通方法(容易理解)
初看題目,第一感覺是豎表轉橫表,首先想到的是使用case when,
所以
第一步操作如下:
select st.stor_name,SUM(sa.qty) as Total, (case when datepart(qq,sa.ord_date)=1 then SUM(sa.qty) else 0 end) as Qtr1, (case when datepart(qq,sa.ord_date)=2 then SUM(sa.qty) else 0 end) as Qtr2, (case when datepart(qq,sa.ord_date)=3 then SUM(sa.qty) else 0 end) as Qtr3, (case when datepart(qq,sa.ord_date)=4 then SUM(sa.qty) else 0 end) as Qtr4 from stores st left join sales sa on st.stor_id=sa.stor_id where DATEPART(yy,sa.ord_date)=1993 group by st.stor_name,sa.ord_date
檢索出結果如下:
這個時候由檢索的結果可知,其中部分商店的統計信息沒有合並統計,原因在於分組的時候我們是按商店名和日期分組的,
第二步操作,將第一步檢索的信息,再次按店名分組統計,sql語句如下:
select A.stor_name as stor_name ,SUM(A.Total) as Total,SUM(A.Qtr1) as Qtr1, SUM(A.Qtr2) as Qtr2,SUM(A.Qtr3) as Qtr3,SUM(A.Qtr4) as Qtr4 from ( --按時間和stor_name分組統計出對應的stor一年的銷售明細 select st.stor_name,SUM(sa.qty) as Total, (case when datepart(qq,sa.ord_date)=1 then SUM(sa.qty) else 0 end) as Qtr1, (case when datepart(qq,sa.ord_date)=2 then SUM(sa.qty) else 0 end) as Qtr2, (case when datepart(qq,sa.ord_date)=3 then SUM(sa.qty) else 0 end) as Qtr3, (case when datepart(qq,sa.ord_date)=4 then SUM(sa.qty) else 0 end) as Qtr4 from stores st left join sales sa on st.stor_id=sa.stor_id where DATEPART(yy,sa.ord_date)=1993 group by st.stor_name,sa.ord_date) as A group by A.stor_name
統計結果如下:
這個時候已經很接近標准答案了,但是還有一個統計行需要統計列出
第三步,將第二步統計的結果再和總計的結果Union一下就可以實現標准的結果
--對每個stor一年的銷售明細進行匯總,之后按stor名分組
select A.stor_name as stor_name ,SUM(A.Total) as Total,SUM(A.Qtr1) as Qtr1, SUM(A.Qtr2) as Qtr2,SUM(A.Qtr3) as Qtr3,SUM(A.Qtr4) as Qtr4 from ( --按時間和stor_name分組統計出對應的stor一年的銷售明細 select st.stor_name,SUM(sa.qty) as Total, (case when datepart(qq,sa.ord_date)=1 then SUM(sa.qty) else 0 end) as Qtr1, (case when datepart(qq,sa.ord_date)=2 then SUM(sa.qty) else 0 end) as Qtr2, (case when datepart(qq,sa.ord_date)=3 then SUM(sa.qty) else 0 end) as Qtr3, (case when datepart(qq,sa.ord_date)=4 then SUM(sa.qty) else 0 end) as Qtr4 from stores st left join sales sa on st.stor_id=sa.stor_id where DATEPART(yy,sa.ord_date)=1993 group by st.stor_name,sa.ord_date) as A group by A.stor_name union --匯總統計信息 select 'Total',SUM(Total),SUM(Qtr1),SUM(Qtr2),SUM(Qtr3),SUM(Qtr4) from ( --每個store一年的銷售明細 select A.stor_name as stor_name ,SUM(A.Total) as Total,SUM(A.Qtr1) as Qtr1, SUM(A.Qtr2) as Qtr2,SUM(A.Qtr3) as Qtr3,SUM(A.Qtr4) as Qtr4 from ( select st.stor_name,SUM(sa.qty) as Total, (case when datepart(qq,sa.ord_date)=1 then SUM(sa.qty) else 0 end) as Qtr1, (case when datepart(qq,sa.ord_date)=2 then SUM(sa.qty) else 0 end) as Qtr2, (case when datepart(qq,sa.ord_date)=3 then SUM(sa.qty) else 0 end) as Qtr3, (case when datepart(qq,sa.ord_date)=4 then SUM(sa.qty) else 0 end) as Qtr4 from stores st left join sales sa on st.stor_id=sa.stor_id where DATEPART(yy,sa.ord_date)=1993 group by st.stor_name,sa.ord_date) as A group by A.stor_name ) as B
執行之后就可以得出我們想要的結果。
總結一下解題的整個思路,首先看題目要求求出每個店鋪每年,每季度的銷售統計,同時最后還要有總計行,統計全年/每個季度的銷售總額。
接着通過case when語句查詢出每個商店每年每季度的銷售總統計,因為是按商店名和時間分組的,所以在查詢出大體的數據結構之后,還需要再對結果進行按商店分組統計,這樣就統計出了符合答案要求的數據,最后在將統計出的結果與以結果為基礎的再次統計union一下就得出了最終的答案。看起來很復雜的一個查詢,只要把思路理清之后一步一步實現就很容易了。
雖然我們經過查詢實現了題目的要求,但是再讓我們回過頭來看看我們的查詢語句,數據少的時候這樣查詢還沒什么問題,但是如果數據量過大就會有很嚴重的性能問題,同時,這樣的sql查詢語句過於龐大,有木有可以優化的方案呢?答案是肯定的。下面就給大家講一下優化的查詢解決方案。
1.2 With rollup + case when count
首先我們的查詢思路還是一下的,先用case when語句構建出大體的查詢框架,唯一不同的是在group by 之后我們多了with rollup語句。代碼如下:
SELECT ISNULL(stor_name,'Total') AS stor_name,SUM(qty) AS Total, SUM(CASE WHEN DATEPART(qq,ord_date)=1 THEN qty ELSE 0 END) AS Qtr1, SUM(CASE WHEN DATEPART(qq,ord_date)=2 THEN qty ELSE 0 END) AS Qtr2, SUM(CASE WHEN DATEPART(qq,ord_date)=3 THEN qty ELSE 0 END) AS Qtr3, SUM(CASE WHEN DATEPART(qq,ord_date)=4 THEN qty ELSE 0 END) AS Qtr4 FROM stores t INNER JOIN sales s ON s.stor_id = t.stor_id WHERE YEAR(s.ord_date) = '1993' GROUP BY stor_name WITH ROLLUP
在group by 之后加上with rollup,我們執行一下查詢語句,就會發現馬上出現了我們想要的結果,這是為什么呢?
在生成包含小計和合計的報表時,ROLLUP 運算符很有用。GROUP BY子句允許一個將額外行添加到簡略輸出端 WITH ROLLUP 修飾符。這些行代表高層(或高聚集)簡略操作。ROLLUP 因而允許你在多層分析的角度回答有關問詢的問題。或者你可以使用 ROLLUP, 它能用一個問詢提供雙層分析。將一個 WITH ROLLUP修飾符添加到GROUP BY 語句,使詢問產生另一行結果,也就是在上例中采用rollup之后,在按stor_name分組之后,還能檢索出本組類的整體聚合信息。
如果有多重分組列的情況時,ROLLUP產生的效果更加復雜。這時,每次在除了最后一個分類列之外的任何列出現一個 “break” (值的改變) ,則問訊會產生一個高聚集累計行。
1.3 With cube + povit
上例中我們講了使用with rullup來實現統計分組,那么還木有比with rollup 更加簡便的查詢呢?答案是肯定的。
首先我們想按照商店和時間分組統計出每家商店每年/季度的銷售情況,這個時候我們需要借助於with cube語句。代碼如下:
select isnull(t.stor_name, 'Total') as 'stor_name', isnull(datepart(qq, ord_date),0) as 'Qtr', sum(qty) as 'qty' from sales s join stores t on s.stor_id = t.stor_id where year(s.ord_date) = 1993 group by datepart(qq, ord_date), t.stor_name with cube
執行結果如下:
With cube語句跟with rollup語句作用很相像,它們的區別在於with CUBE 生成的結果集顯示了所選列中值的所有組合的聚合,而with ROLLUP 生成的結果集顯示了所選列中值的某一層次結構的聚合
第二步,我們將原始數據經過第一步的查詢之后轉換成了個標准的豎表,下邊要做的就是如何將這個豎表轉換成橫表,我們在上邊都是用case when的語法來實現這種表的橫豎轉換,這里我們換一種方式來實現。這里我們用povit方法來實現。代碼如下:
select stor_name, isnull([0],0) as 'Total', isnull([1],0) as 'Qtr1',isnull([2],0) as 'Qtr2', isnull([3],0) as 'Qtr3', isnull([4],0) as 'Qtr4' from ( select isnull(t.stor_name, 'Total') as 'stor_name', isnull(datepart(qq, ord_date),0) as 'Qtr', sum(qty) as 'qty' from sales s join stores t on s.stor_id = t.stor_id where year(s.ord_date) = 1993 group by datepart(qq, ord_date), t.stor_name with cube ) as tmp pivot ( sum(qty) for Qtr in ([0], [1], [2], [3], [4]) ) as pvt
上邊代碼示例中高亮部分即為使用pivot進行表的橫豎轉換的關鍵代碼。
PIVOT用於行轉列,在SQL Server 2000可以用聚合函數配合CASE語句實現,
PIVOT的一般語法是:PIVOT(聚合函數(列) FOR 列 in (…) )AS P
這跟我們上邊示例中使用的高亮標注的部分的方法是一樣的
總結:
通過這樣一個簡單的查詢,引出了今天要講的表的行列轉換(case when 和 pivot兩種方法),表數據的統計(with rollup 和with cube方法),這也就達到了總結的目的。重要的不是講這些方法怎么怎么用,主要是講求解決問題的一個思路,以及在解決問題后對性能及效率的優化,希望可以對大家有些幫助。