某電商銷售數據分析 SQL 面試題解析

本文基於微信羣裏的一個問題,感謝這位朋友提供的分享。

題目描述

假設某電商銷售數據有如下幾張表:

Brand(品牌表)

bid name
1 品牌1

Category(品類表)

cid name
1 食品

Monthlysales(月度銷量統計表)

month bid cid paltform sales
2019-12-01 1 1 1 1000

其中,

  • month:date 類型,存儲的是每個月的第一天;
  • bid:對應 Brand.bid;
  • cid:對應 Category.cid;
  • paltform:對應不同的電商平臺,有 2 種:1,2;
  • sales:銷售額;
  • 主鍵爲 (month, paltform, bid, cid),一行數據對應一個月一個平臺一個 bid 的一個 cid 的銷售額。

基於上面的描述,我們可以創建以下示例表和模擬數據(使用 MySQL 數據庫):

create table brand(bid int auto_increment primary key, name varchar(50));
insert into brand(name) values ('品牌1');
insert into brand(name) values ('品牌2');
insert into brand(name) values ('品牌3');
insert into brand(name) values ('品牌4');
insert into brand(name) values ('品牌5');

create table category(cid int auto_increment primary key, name varchar(10));
insert into category(name) values ('食品');
insert into category(name) values ('手機');
insert into category(name) values ('服飾');
insert into category(name) values ('圖書');
insert into category(name) values ('電腦');

create table monthlysales(month date, bid int, cid int, platform int,sales int);
alter table monthlysales add constraint pk_monthlysales primary key(month, platform, bid, cid);

insert into monthlysales
with recursive dt as (
  select date '2019-01-01' as v
  union all
  select v + interval '1' month from dt where v < date '2019-12-01'
),
platform(pid) as (
  values row(1), row(2)
)
select v, bid, cid, pid, 1000 + round((rand(1) * 500))
from dt
cross join brand
cross join category
cross join platform;

爲了生成示例數據,我們使用了遞歸形式的通用表表達式(WITH子句),相關語法可以參考這篇文章。另外,rand(1) 函數確保了結果的可復現。

問題 1

對於指定的 cid 範圍(cid 列表:1,3,5),查詢 2019 年每個平臺上每個 bid 對應每個 cid 的累計銷售額,輸出格式如下:

bid brand_name cid category_name platform total_sales

請寫出 SQL。

解析

這個題目比較簡單,就是按照品牌、品類以及平臺分組再加上 sum 函數統計銷售額;同時關聯其他表獲取品牌和品類名稱。

select b.bid, b.name brand_name, c.cid, c.name category_name, s.platform, sum(s.sales) total_sales
from monthlysales s
join brand b on (b.bid = s.bid)
join category c on (c.cid = s.cid)
where b.bid in (1, 3, 5)
and s.month between date '2019-01-01' and date '2019-12-01'
group by b.bid, b.name, c.cid, c.name, s.platform;

bid|brand_name  |cid|category_name  |platform|total_sales|
---|------------|---|---------------|--------|-----------|
  1|品牌1       |  1|食品           |       1|      15115|
  1|品牌1       |  2|手機           |       1|      15441|
  1|品牌1       |  3|服飾           |       1|      14869|
  1|品牌1       |  4|圖書           |       1|      15516|
  1|品牌1       |  5|電腦           |       1|      14971|
...

對於 2019 年的判斷,最好不要使用 year(month) 函數,因爲這樣會導致索引失效。另外,關於各種數據庫中的分組彙總和聚合函數可以參考這篇文章

問題 2

查詢 2019 年有 5 個以上(包含 5 個)不同 cid 的單月單平臺銷售額大於等於 1480 的品牌列表,以及對應的不同 cid 數量,輸出格式如下:

bid brand_name cid_count

解析

這個問題和上面的問題差不多,主要是按照品牌分組統計;但是在分組統計之後,還需要過濾一下統計的數量。

select b.bid, b.name brand_name, count(distinct s.cid) cid_count
from monthlysales s
join brand b on (b.bid = s.bid)
where s.month between date '2019-01-01' and date '2019-12-01'
and s.sales >= 1480
group by b.bid, b.name
having cid_count >= 5;

bid|brand_name  |cid_count|
---|------------|---------|
  4|品牌4       |        5|

其中,count() 函數用於統計各種品牌單月在單平臺上不同 cid 的數量;having 最終返回數量大於等於 5 的品牌。

問題 3

查詢 2019 年只在平臺 1 上有銷售額的品牌中(排除平臺爲 2 時銷售累計額大於 0 的品牌),平臺 1 累計銷售額最大的 Top 3 品牌以及對應的銷售額,輸出格式如下:

bid brand_name total_sales_p1

解析

同樣是先進行分組統計,獲取每個品牌在平臺 1 上的累計銷售額;然後加上排序和數量限定操作返回 Top 3 結果。

select b.bid, b.name brand_name, sum(s.sales) total_sales_p1
from monthlysales s
join brand b on (b.bid = s.bid)
where s.month between date '2019-01-01' and date '2019-12-01'
and s.platform = 1
group by b.bid, b.name
having total_sales_p1 > 0
order by total_sales_p1 desc
limit 3;

bid|brand_name  |total_sales_p1|
---|------------|--------------|
  3|品牌3       |         76464|
  1|品牌1       |         75912|
  4|品牌4       |         74931|

其中,ORDER BY用於按照累計銷售額從高到低進行排序;LIMIT用於返回前 3 條記錄。

問題 4

查詢 2019 年在兩個平臺中分別同時都能進入銷售額 Top 3 的品牌以及對應的全平臺累計銷售額,輸出格式如下:

bid brand_name total_sales_all

解析

這個問題至少有兩種解決辦法:基於問題 3 中的解決方法分別獲取平臺 1 和平臺 2 上的 Top 3 品牌,然後使用一個連接查詢;或者使用 MySQL 8.0 中的窗口函數。

第一種方法的實現如下:

select top3_p1.bid, top3_p1.brand_name, top3_p1.total_sales_p1 + top3_p2.total_sales_p2 as total_sales_all
from (
  select b.bid, b.name brand_name, sum(s.sales) total_sales_p1
  from monthlysales s
  join brand b on (b.bid = s.bid)
  where s.month between date '2019-01-01' and date '2019-12-01'
  and s.platform = 1
  group by b.bid, b.name
  having total_sales_p1 > 0
  order by total_sales_p1 desc
  limit 3) top3_p1
join (
  select b.bid, b.name brand_name, sum(s.sales) total_sales_p2
  from monthlysales s
  join brand b on (b.bid = s.bid)
  where s.month between date '2019-01-01' and date '2019-12-01'
  and s.platform = 2
  group by b.bid, b.name
  having total_sales_p2 > 0
  order by total_sales_p2 desc
  limit 3) top3_p2
on (top3_p1.bid = top3_p2.bid)
order by total_sales_all desc;

bid|brand_name  |total_sales_all|
---|------------|---------------|
  1|品牌1       |         153579|
  3|品牌3       |         152819|
  4|品牌4       |         150860|

其中,top3_p1 代表了平臺 1 上的 Top 3 品牌和累計銷售額;op3_p2 代表了平臺 2 上的 Top 3 品牌和累計銷售額。

如果使用的 MySQL 8.0 版本,我們可以利用窗口函數計算排名:

with sales2019 as (
  select bid, platform, sum(sales) sales
  from monthlysales 
  where month between date '2019-01-01' and date '2019-12-01'
  group by bid, platform
),
top3 as (
  select * from (
  select bid, platform, sum(sales) over (partition by bid) total_sales_all, 
         rank() over (partition by platform order by sales desc) as rk
  from sales2019 
  ) t where rk <= 3
)
select p1.bid, b.name brand_name, p1.total_sales_all
from top3 p1 
join top3 p2 on (p1.bid = p2.bid)
join brand b on (b.bid = p1.bid)
where p1.platform = 1
and p2.platform = 2;

bid|brand_name  |total_sales_all|
---|------------|---------------|
  1|品牌1       |         153579|
  3|品牌3       |         152819|
  4|品牌4       |         150860|

其中,sales2019 代表了 2019 年各種品牌在不同平臺上的銷售額;top3 基於 sales2019 計算不同平臺上的 Top 3 品牌和累計銷售額;這裏使用了兩個窗口函數(OVER子句是窗口函數的標識),sum() 用於獲取品牌的累計銷售額,rank() 用於計算不同平臺上的銷售額排名。

除此之外,WITH語句比第一種方法語義上更加清晰;而且從性能角度來說也會更好,因爲它只需要訪問 monthlysales 表一次。

總結

總的來說,這幾道題目都是高頻的 SQL 數據分析問題。依次考察了GROUP BY分組加聚合函數、HAVING過濾和DISTINCT去重、ORDER BY排序加上LIMIT子句實現 Top-N 排行榜,以及窗口函數的使用。

定期更新數據庫領域相關文章,歡迎關注❤️、評論📝、點贊👍!

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