SQL 數據分析:銷售數據的小計/合計/總計以及數據透視表

在這裏插入圖片描述

學習過 SQL 的人都知道,使用聚合函數(AVG、SUM、COUNT、MIN/MAX 等)和分組操作(GROUP BY)可以對數據進行基本的統計分析,例如統計公司員工的人數、每個部門的平均月薪等。如果想要回顧這些基礎概念,可以參考這篇文章

不過 SQL 不僅僅能夠進行這些基本的分組彙總,它還提供了許多高級的統計分析功能。本文就來介紹一下如何使用 SQL 實現銷售數據的小計、合計、總計以及多維度交叉統計和數據透視表。我們首先列出這些功能在主流數據庫中的支持情況:

功能 Oracle MySQL SQL Server PostgreSQL
GROUP BY ✔️ ✔️ ✔️ ✔️
GROUP BY ROLLUP ✔️ ✔️* ✔️ ✔️
GROUP BY CUBE ✔️ ✔️ ✔️
GROUP BY GROUPING SETS ✔️ ✔️ ✔️
數據透視表 ✔️ ✔️ ✔️ ✔️

* 參考下文中的具體討論。本文使用的示例數據可以點此下載,如果沒有特殊說明,以下示例適用於上面的 5 種數據庫。

小計、合計與總計

我們先查看一下示例表中的數據:

select * from sales_data;
saledate  |product  |channel  |amount |
----------|---------|---------|-------|
2019-01-01|桔子     |淘寶     |1864.00|
2019-01-01|桔子     |京東     |1329.00|
2019-01-01|桔子     |店面     |1736.00|
2019-01-01|香蕉     |淘寶     |1573.00|
2019-01-01|香蕉     |京東     |1364.00|
2019-01-01|香蕉     |店面     |1178.00|
2019-01-01|蘋果     |淘寶     | 511.00|
2019-01-01|蘋果     |京東     | 568.00|
2019-01-01|蘋果     |店面     | 847.00|

這是一個模擬的銷售數據,記錄了不同日期(2019-01-01 到 2019-06-30)、三種不同產品、三種不同渠道的銷量情況。

以下語句使用GROUP BY統計了三種不同產品各自的總銷量:

select product, sum(amount)
from sales_data
group by product;
product  |sum(amount)|
---------|-----------|
桔子     |  909261.00|
香蕉     |  925369.00|
蘋果     |  937052.00|

顯然,還可以編寫 SQL 語句統計三種不同產品在不同渠道各自的銷量合計、所以產品的銷量總計等。但是如何一次獲取這些按照不同維度進行統計的結果呢?我們可以使用GROUP BY的第一個擴展選項:ROLLUP。例如:

-- Oracle、SQL Server、PostgreSQL
select product "產品", channel "渠道", sum(amount) "銷量"
from sales_data
group by rollup (product, channel);
產品|渠道|銷量       |
----|----|----------|
桔子|店面| 294680.00|
桔子|京東| 311799.00|
桔子|淘寶| 302782.00|
桔子|NULL| 909261.00|
蘋果|店面| 306643.00|
蘋果|京東| 318614.00|
蘋果|淘寶| 311795.00|
蘋果|NULL| 937052.00|
香蕉|店面| 311445.00|
香蕉|京東| 306033.00|
香蕉|淘寶| 307891.00|
香蕉|NULL| 925369.00|
NULL|NULL|2771682.00|

其中,ROLLUP表示先按照 (product, channel) 的組合計算不同產品、不同渠道的銷量小計,然後按照計算不同產品(product)、所有渠道的銷量和計(結果中的 channel 字段顯示爲 NULL),最後計算所有產品、所有渠道的銷量總計(結果中的 product 和 channel 字段都爲 NULL)。

如果使用 MySQL 數據庫,ROLLUP的使用略有不同:

-- MySQL
select product, channel, sum(amount)
from sales_data
group by product, channel with rollup;

MySQL 在分組字段之後使用with rollup選項,查詢的結果與其他數據庫相同。

ROLLUP選項可以使用UNION合併多個查詢結果進行模擬:

with d as (
  select product, channel , sum(amount) amount
  from sales_data
  group by product, channel
)
select product "產品", channel "渠道", amount "銷量" from d
union all
select product, NULL, sum(amount) from d group by product
union all
select NULL, NULL, sum(amount) from d;

其中,WITH表示定義通用表表達式,類似於臨時表;關於通用表表達式的概念可以參考這篇文章。以上語句正好解釋了ROLLUP選項的作用。

📝GROUP BY子句的ROLLUP選項是一種按照層次從下往上依次彙總的過程,需要彙總 N + 1 個維度,N 是分組字段的個數。

多維度交叉統計

如果我們的銷量報表需要統計以下信息:

  • 不同產品、不同渠道的銷量小計;
  • 不同產品、所有渠道的銷量合計;
  • 所有產品、不同渠道的銷量合計;
  • 所有產品、所有渠道的銷量總計。

由於ROLLUP選項是按照分組字段的順序依次往上匯聚,(product, channel) 無法獲取所有產品、不同渠道的銷量合計,(channel, product) 又無法獲取不同產品、所有渠道的銷量合計。雖然可以查詢兩次然後去除重複結果,但是不方便;況且當我們的分組字段增加到 3 個或者 4 個時,組合情況更多。

爲此,我們可以使用GROUP BY的第二個擴展選項:CUBE。以下語句可以實現上面的統計需求:

-- Oracle、SQL Server、PostgreSQL
select coalesce(product, '【全部產品】') "產品", coalesce(channel, '【所有渠道】') "渠道", sum(amount) "銷量"
from sales_data
group by cube (product,channel)
order by product, channel;
產品       |渠道      |銷量    |
-----------|-----------|-------|
桔子       |京東       | 311799|
桔子       |店面       | 294680|
桔子       |淘寶       | 302782|
桔子       |【所有渠道】| 909261|
蘋果       |京東       | 318614|
蘋果       |店面       | 306643|
蘋果       |淘寶       | 311795|
蘋果       |【所有渠道】| 937052|
香蕉       |京東       | 306033|
香蕉       |店面       | 311445|
香蕉       |淘寶       | 307891|
香蕉       |【所有渠道】| 925369|
【全部產品】|京東       | 936446|
【全部產品】|店面       | 912768|
【全部產品】|淘寶       | 922468|
【全部產品】|【所有渠道】|2771682|

爲了更好地查看結果,我們使用 coalesce 函數對 NULL 進行了轉換顯示;CUBE基於分組字段的任意組合進行彙總,比ROLLUP獲得的更多維度的統計信息。

MySQL 目前沒有提供CUBE選項。以上示例中的分組字段只有 2 個,可以使用UNION合併多個查詢結果進行模擬:

with d as (
  select product, channel , sum(amount) amount
  from sales_data
  group by product, channel
)
select product "產品", channel "渠道", amount "銷量" from d
union all
select product, NULL, sum(amount) from d group by product
union all
select NULL, channel, sum(amount) from d group by channel
union all
select NULL, NULL, sum(amount) from d;

這種方法正好解釋了CUBE選項的作用,也適用於其他數據庫;但是如果分組字段達到 3 個以上就比較麻煩。

📝GROUP BY子句的CUBE選項是一種按照各種層次組合彙總的過程,需要彙總 2 的 N 次方個維度,N 是分組字段的個數。

自定義統計維度

ROLLUPCUBE都是按照預定義好的維度進行數據統計,SQL 還提供了第三GROUP BY個選項:GROUPING SETS。它允許我們指定自定義的分組集,例如:

GROUP BY ROLLUP (product, channel)

實際上等價於:

GROUP BY GROUPING SETS ((product, channel), (product), ())

其中,每一個維度都位於一對括號之內。(product, channel) 表示按照產品和渠道的組合進行統計、(product) 表示按照產品進行統計、() 表示所有數據進行統計。

同樣,對於CUBE選項:

GROUP BY CUBE (product, channel)

等價於:

GROUP BY GROUPING SETS ((product, channel), (product), (channel), ())

而沒有任何選項的GROUP BY

GROUP BY product, channel

等價於:

GROUP BY GROUPING SETS ((product, channel))

注意,(product, channel) 需要使用括號包含;GROUP BY GROUPING SETS (product, channel)統計的是產品維度的數據和渠道維度的數據。

以下示例用於計算按照季度、產品、渠道以及不同季度不同渠道的銷量統計:

-- Oracle、PostgreSQL
select coalesce(to_char(saledate,'Q'),'【半年】') "季度", coalesce(product, '【全部產品】') "產品", 
       coalesce(channel, '【所有渠道】') "渠道", sum(amount) "銷量"
from sales_data
group by grouping sets ((to_char(saledate,'Q')), (product), (channel), (to_char(saledate,'Q'), channel));
季度    |產品        |渠道       |銷量   |
--------|------------|-----------|-------|
1       |【全部產品】|京東       | 396027|
2       |【全部產品】|京東       | 540419|
1       |【全部產品】|店面       | 388885|
2       |【全部產品】|店面       | 523883|
1       |【全部產品】|淘寶       | 401937|
2       |【全部產品】|淘寶       | 520531|
【半年】|【全部產品】|京東        | 936446|
【半年】|【全部產品】|店面        | 912768|
【半年】|【全部產品】|淘寶        | 922468|
1       |【全部產品】|【所有渠道】|1186849|
2       |【全部產品】|【所有渠道】|1584833|
【半年】|香蕉        |【所有渠道】| 925369|
【半年】|桔子        |【所有渠道】| 909261|
【半年】|蘋果        |【所有渠道】| 937052|

我們使用了 to_char(saledate,‘Q’) 函數獲取銷售日期對應的季度;如果使用 SQL Server 數據庫,可以換成 datename(q, saledate) 函數;MySQL 目前沒有提供CUBE選項。

從上面的示例可以看出,GROUPING SETS可以實現任意的維度統計;ROLLUPCUBE都屬於預定義的特定統計維度。

數據透視表

在 Excel 中有一個分析功能叫做數據透視表(Pivot Table),如下圖所示:

Pivot Table
其中,產品和渠道出現在行中,可以進行展開和摺疊;日期出現在列中,最後一列是所有月份的彙總;透視表可以通過將行移動到列或將列移動到行,從而查看不同級別的數據彙總。

使用 SQL 同樣可以實現上面的數據透視表:

-- Oracle、PostgreSQL
with d(saledate, product, channel, amount) as (
  select extract(month from saledate), product, channel, sum(amount)
  from sales_data
  group by extract(month from saledate), product, channel
)
select coalesce(product, '【全部產品】') "產品", coalesce(channel, '【所有渠道】') "渠道", 
       sum(case saledate when 1 then amount else 0 end) "1月",
       sum(case saledate when 2 then amount else 0 end) "2月",
       sum(case saledate when 3 then amount else 0 end) "3月",
       sum(case saledate when 4 then amount else 0 end) "4月",
       sum(case saledate when 5 then amount else 0 end) "5月",
       sum(case saledate when 6 then amount else 0 end) "6月",
       sum(amount) "合計"
from d
group by rollup (product, channel)
order by product, channel desc;

Pivot Table
首先,使用WITH獲得了按照月份、產品、渠道統計的銷量;extract(month from saledate) 函數用於提取銷售日期中的月份;然後使用 SUM 函數和CASE表達式將月份從行轉換爲列,最終得到了一個數據透視表。

MySQL 中需要使用with rollup;SQL Server 中需要使用 datepart(month, saledate) 函數。

總結

Excel 中的數據透視表通過展開、摺疊、篩選、行列轉換等操作得到不同層次和視角的數據小計/合計/總計。這些功能在數據庫的 OLAP(在線分析處理系統)中被稱爲下鑽(Drill down)、上鑽(Roll up)、切塊(Dicing)、切片(Slicing)、旋轉(Pivot)等,利用 SQL 中的聚合函數、GROUP BY子句的ROLLUPCUBEGROUPING SETS選項以及CASE表達式實現,而且更加靈活。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章