每日學習心得:SQL查詢表的行列轉換/小計/統計(with rollup,with cube,pivot解析)


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方法),這也就達到了總結的目的。重要的不是講這些方法怎么怎么用,主要是講求解決問題的一個思路,以及在解決問題后對性能及效率的優化,希望可以對大家有些幫助。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM