30道經(jīng)典SQL面試題講解(21-30)
本篇節(jié)選自書籍《對比Excel,輕松學(xué)習(xí)SQL數(shù)據(jù)分析》一書,主要講解數(shù)據(jù)分析面試中常見的30道SQL面試題。
1-20道可以看:
21 獲取新增用戶數(shù)
現(xiàn)在有一個用戶表user_reg_table,這張表存儲了每位用戶的uid(用戶id)、reg_time(注冊時間)等其他信息,我們想知道某一天的新增用戶數(shù),以及該天對應(yīng)的過去7天內(nèi)每天平均新增用戶數(shù),該怎么實現(xiàn)呢?
user_reg_table表如下所示:
| uid | reg_time |
|---|---|
| 1 | 2019/12/25 10:00:00 |
| 2 | 2019/12/26 10:00:00 |
| 3 | 2019/12/27 10:00:00 |
| 4 | 2019/12/28 10:00:00 |
| 5 | 2019/12/29 10:00:00 |
| 6 | 2019/12/30 10:00:00 |
| 7 | 2019/12/31 10:00:00 |
| 8 | 2020/1/1 10:00:00 |
| 9 | 2020/1/2 10:00:00 |
| 10 | 2020/1/3 10:00:00 |
| 11 | 2020/1/4 10:00:00 |
自己先想一下代碼怎么寫,然后再參考我的代碼。
set @day_date = "2020-01-01";
select
count(if(date(reg_time) = @day_date,uid,null)) as new_cnt
,count(uid)/7 as 7_avg_cnt
from
demo.user_reg_table
where
date(reg_time) between date_sub(@day_date,interval 6 day) and @day_date
解題思路:
我們是想知道某一天的用戶數(shù),這個某一天是一個可變的值,所以我們想到了變量,通過設(shè)置變量來達到日期的變化;其次我們還需要過去7天,在變量的基礎(chǔ)上減去6天即可,這里面需要注意的是,我們用的between用來篩選介于過去7天和今天之間的用戶,而不能直接使用大于7天前日期的這個條件,因為大于7天前的日期很有可能包括你設(shè)置的變量后面的日期。最后運行結(jié)果如下:
| new_cnt | 7_avg_cnt |
|---|---|
| 1 | 1 |
22 獲取用戶首次購買時間
現(xiàn)在我們有一張表first_order_table,這張表中包含了order_id(訂單id)、uid(用戶id)、order_time(訂單時間),我們想知道每個用戶的首次購買時間,以及是否在最近7天內(nèi),該怎么實現(xiàn)呢?
first_order_table表如下所示:
| order_id | uid | order_time |
|---|---|---|
| 201901 | 1 | 2020/1/1 10:00:00 |
| 201902 | 2 | 2020/1/2 10:00:00 |
| 201903 | 3 | 2020/1/3 10:00:00 |
| 201904 | 1 | 2020/1/4 10:00:00 |
| 201905 | 2 | 2020/1/5 10:00:00 |
| 201906 | 3 | 2020/1/6 10:00:00 |
| 201907 | 1 | 2020/1/7 10:00:00 |
| 201908 | 2 | 2020/1/8 10:00:00 |
| 201909 | 3 | 2020/1/9 10:00:00 |
| 201910 | 1 | 2020/1/10 10:00:00 |
| 201911 | 2 | 2020/1/11 10:00:00 |
自己先想一下代碼怎么寫,然后再參考我的代碼。
select
t1.uid
,t1.first_time
,(date(t1.first_time) > date_sub(curdate(),interval 6 day)) is_7_day
from
(select
uid
,min(order_time) first_time
from
demo.first_order_table
group by
uid
)t1
解題思路:
我們主要有兩個事情,第一件事就是獲取每個用戶的首次購買時間,其實就是最小時間,然后再對最小時間和最近7天進行比較,得出首次購買時間是否在最近7天。最后運行結(jié)果如下:
| uid | first_time | is_7_day |
|---|---|---|
| 1 | 2020-01-01 10:00:00 | 0 |
| 2 | 2020-01-02 10:00:00 | 0 |
| 3 | 2020-01-03 10:00:00 | 0 |
23 同時獲取用戶和訂單數(shù)據(jù)
還是前面的兩張表user_reg_table和first_order_table,現(xiàn)在我們想知道過去7天每天的新增用戶數(shù)、訂單數(shù)、下單用戶數(shù),該怎么實現(xiàn)呢?
自己先想一下代碼怎么寫,然后再參考我的代碼。
set @day_date = "2020-01-04";
select
t1.tdate
,t1.new_cnt
,t2.order_cnt
,t2.uid_cnt
from
(
select
date(reg_time) tdate
,count(uid) new_cnt
from
demo.user_reg_table
where
date(reg_time) between date_sub(@day_date,interval 6 day) and @day_date
group by
date(reg_time)
)t1
left join
(
select
date(order_time) tdate
,count(order_id) order_cnt
,count(distinct uid) uid_cnt
from
demo.first_order_table
where
date(order_time) between date_sub(@day_date,interval 6 day) and @day_date
group by
date(order_time)
)t2
on t1.tdate = t2.tdate
解題思路:
我們要獲取每天的新增用戶數(shù)以及訂單數(shù),新增用戶數(shù)和訂單數(shù)據(jù)是存儲在兩個不同的表中,所以我們可以先分別獲取每天的新增用戶數(shù)和每天的訂單數(shù),然后再根據(jù)日期把兩個表拼接在一起。最后運行結(jié)果如下:
| tdate | new_cnt | order_cnt | uid_cnt |
|---|---|---|---|
| 2019-12-29 | 1 | null | null |
| 2019-12-30 | 1 | null | null |
| 2019-12-31 | 1 | null | null |
| 2020-01-01 | 1 | 1 | 1 |
| 2020-01-02 | 1 | 1 | 1 |
| 2020-01-03 | 1 | 1 | 1 |
| 2020-01-04 | 1 | 1 | 1 |
24 隨機抽樣
還是前面的兩張表user_reg_table和first_order_table,現(xiàn)在我們想要從用戶表中隨機抽取5位用戶,以及這5位用戶的歷史購買訂單數(shù),想想該怎么實現(xiàn)呢?
自己先想一下代碼怎么寫,然后再參考我的代碼。
select
user_table.uid
,t.order_cnt
from
demo.user_reg_table user_table
left join
(
select
uid
,count(order_id) as order_cnt
from
demo.first_order_table
group by
uid
)t
on user_table.uid = t.uid
order by rand()
limit 5
解題思路:
我們要隨機獲取5位用戶的歷史購買訂單數(shù),首先需要生成每個用戶歷史的購買訂單數(shù),然后再從中隨機抽取5位。具體的隨機抽取規(guī)則為:利用rand()生成隨機數(shù),然后再利用order by進行排序,最后利用limit把前5條顯示出來。最后運行結(jié)果如下:
| uid | order_cnt |
|---|---|
| 9 | null |
| 3 | 3 |
| 8 | null |
| 5 | null |
| 11 | null |
25 獲取沉默用戶數(shù)
還是前面的兩張表user_reg_table和first_order_table,現(xiàn)在我們想獲取沉默用戶的數(shù)量,沉默的定義是已注冊但是最近30天內(nèi)沒有購買記錄的人,該怎么實現(xiàn)呢?
自己先想一下代碼怎么寫,然后再參考我的代碼。
select
count(user_table.uid) chenmo_cnt
from
demo.user_reg_table user_table
left join
(
select
uid
from
demo.first_order_table
where
date(order_time) < date_sub(curdate(),interval 29 day)
group by
uid
)t
on user_table.uid = t.uid
where
t.uid is null
解題思路:
我們要獲取近30天沒有購買記錄的人,可以先把最近30天內(nèi)有購買記錄的人取出來,然后用user_table表中的uid去拼接最近30天有購買記錄的人,如果不能拼接到,即拼接結(jié)果為null,就表示這部分人最近30天沒有購買。把null的部分取出來,然后對uid進行計數(shù)即可。最后運行結(jié)果為14,因為我們是用的curdate(),所以不同時間運行得到的結(jié)果會是不一樣的。
26 獲取新用戶的訂單數(shù)
還是前面的兩張表user_reg_table和first_order_table,現(xiàn)在我們想獲取最近7天注冊新用戶在最近7天內(nèi)的訂單數(shù)是多少,該怎么實現(xiàn)呢?
自己先想一下代碼怎么寫,然后再參考我的代碼。
select
sum(t2.order_cnt)
from
(
select
uid
from
demo.user_reg_table
where
date(reg_time) > date_sub(curdate(),interval 6 day)
)t1
left join
(
select
uid
,count(order_id) order_cnt
from
demo.first_order_table
where
date(order_time) > date_sub(curdate(),interval 6 day)
group by
uid
)t2
on t1.uid = t2.uid
解題思路:
我們要獲取最近7天注冊新用戶在最近7天內(nèi)的訂單數(shù),首先獲取最近7天新注冊的用戶,然后獲取每個用戶在最近7天內(nèi)的訂單數(shù),最后將兩個表進行拼接,且新用戶表為主表,進行左連接。最后運行結(jié)果為14,不同時間運行得到的結(jié)果會是不一樣的。
27 獲取借款到期名單
現(xiàn)在有一張借款表loan_table,這張表記錄了每筆借款的id、loan_time(借款時間)、expire_time(到期時間)、reback_time(還款時間)、amount(金額)、status(還款狀態(tài),1表示已還款、0表示未還款),我們想要獲取每天到期的借款筆數(shù)、借款金額和平均借款天數(shù),該怎么實現(xiàn)呢?
loan_table表如下所示:
| id | loan_time | expire_time | reback_time | amount | status |
|---|---|---|---|---|---|
| 1 | 2019/12/1 | 2019/12/31 | 2208 | 0 | |
| 2 | 2019/12/1 | 2019/12/31 | 2019/12/31 | 5283 | 1 |
| 3 | 2019/12/5 | 2020/1/4 | 5397 | 0 | |
| 4 | 2019/12/5 | 2020/1/4 | 4506 | 0 | |
| 5 | 2019/12/10 | 2020/1/9 | 3244 | 0 | |
| 6 | 2019/12/10 | 2020/1/9 | 2020/1/12 | 4541 | 1 |
| 7 | 2020/1/1 | 2020/1/31 | 2020/1/10 | 3580 | 1 |
| 8 | 2020/1/1 | 2020/1/31 | 7045 | 0 | |
| 9 | 2020/1/5 | 2020/2/4 | 2067 | 0 | |
| 10 | 2020/1/5 | 2020/2/4 | 7225 | 0 |
自己先想一下代碼怎么寫,然后再參考我的代碼。
select
count(id) as loan_cnt
,sum(amount) as loan_amount
,avg(datediff(reback_time,loan_time)) avg_day
from
demo.loan_table
where
expire_time = curdate()
解題思路:
我們是要獲取每天到期的數(shù)據(jù),只需要通過篩選到期時間等于當天把當天到期的數(shù)據(jù)篩選出來,然后對id進行計數(shù)得到到期筆數(shù),對amount進行求和得到到期金額,對還款時間和借款時間做差取平均得到平均借款天數(shù),注意這里是用的還款時間和借款時間做差,而非到期時間和借款時間做差,因為有可能提前還款或逾期。最后運行結(jié)果為空,表示今天沒有到期的借款。
28 獲取即將到期的借款信息
還是前面的借款表loan_table,現(xiàn)在我們想知道有多少筆借款會在未來7天內(nèi)到期,其中有多少筆是已經(jīng)還款的,該怎么實現(xiàn)呢?
自己先想一下代碼怎么寫,然后再參考我的代碼。
select
count(id) as loan_cnt
,count(if(status = 1,id,null)) as reback_cnt
from
demo.loan_table
where
expire_time between curdate() and date_sub(curdate(),interval 6 day)
解題思路:
我們是要獲取未來7天內(nèi)要到期的借款筆數(shù)和其中已經(jīng)還款的筆數(shù),首先把最近7天內(nèi)要到期的數(shù)據(jù)篩選出來,然后再通過還款狀態(tài)status進行判斷,再獲取已還款的筆數(shù)。最后運行結(jié)果為空。
29 獲取歷史逾期借款信息
還是前面的借款表loan_table,現(xiàn)在我們想知道歷史逾期的筆數(shù)和金額以及至今還逾期的筆數(shù)和金額,該怎么實現(xiàn)呢?
自己先想一下代碼怎么寫,然后再參考我的代碼。
select
count(id) as loan_cnt
,sum(amount) as loan_amount
,count(if(status = 0,id,null)) as no_reback_cnt
,sum(if(status = 0,amount,0)) as no_reback_amount
from
demo.loan_table
where
(reback_time > expire_time)
or (reback_time is null and expire_time < curdate())
解題思路:這里面的關(guān)鍵信息在于逾期怎么判斷,逾期是用到期時間和還款時間去進行比較,如果是逾期且現(xiàn)在已經(jīng)還款了的,可以直接比較到期時間和還款時間,如果還款時間大于到期時間,說明是逾期的;還有一種是逾期且現(xiàn)在還未還款的,這種情況是沒有還款時間的,也就是還款時間是空,但是到期時間是在今天之前,說明已到期但是未還款。最后運行結(jié)果如下:
| loan_cnt | loan_amount | no_reback_cnt | no_reback_amount |
|---|---|---|---|
| 5 | 19896 | 4 | 15355 |
30 綜合實戰(zhàn)
這一題是我們最后一道實戰(zhàn)題,給大家還原一下我們在前面梳理數(shù)據(jù)庫邏輯的時候遇到的情況。假如你現(xiàn)在剛?cè)肼氁患倚碌碾娚坦荆阈枰ㄟ^一個Sql把電商整個漏斗轉(zhuǎn)化環(huán)節(jié)的數(shù)據(jù)全部取出來:主要當日總瀏覽量、瀏覽人數(shù)、加購物車數(shù)、加購物車人數(shù)、訂單數(shù)、下單人數(shù)、確認收貨訂單數(shù),該怎么寫。已知有如下幾張表:
browse_log_table(瀏覽記錄表):id(瀏覽id)、product_id(商品id)、uid(用戶id)、channel(渠道)、browse_time(瀏覽時間)......;
cart_table(購物車詳情表):id(購物車id)、browse_id(瀏覽id)、cart_time(加購物車時間)......;
order_table(訂單詳情表):id(訂單id)、cart_id(購物車id)、order_time(訂單時間)、amount(訂單金額)......;
take_table(收貨詳情表):order_id(訂單id)、take_time(確認收貨時間)......。
select
count(browse_log_table.id) as browse_cnt
,count(distinct browse_log_table.uid) as browse_uid_cnt
,count(cart_table.id) as cart_cnt
,count(distinct if(cart_table.id is not null,browse_log_table.uid,null)) as cart_uid_cnt
,count(order_table.id) as order_cnt
,count(distinct if(order_table.id is not null,browse_log_table.uid,null)) as order_uid_cnt
,count(take_table.id) as take_cnt
,count(distinct if(take_table.id is not null,browse_log_table.uid,null)) as take_uid_cnt
from
browse_log_table
left join
cart_table
on browse_log_table.id = cart_table.browse_id
left join
order_table
on cart_table.id = order_table.cart_id
left join
take_table
on order_table.id = take_table.order_id
where
browse_log_table.browse_time = curdate()
想進一步了解更多內(nèi)容的同學(xué),可以閱讀《對比Excel,輕松學(xué)習(xí)SQL數(shù)據(jù)分析》一書。

▊《對比Excel,輕松學(xué)習(xí)SQL數(shù)據(jù)分析》
張俊紅 著
學(xué)習(xí)SQL 的主要原因是工作需要。網(wǎng)上關(guān)于數(shù)據(jù)相關(guān)崗位的招聘都要求有熟練使用SQL 這一條,為什么會這樣呢?這是因為我們負責的是與數(shù)據(jù)相關(guān)的工作,而獲取數(shù)據(jù)是我們工作的第一步,比如,你要通過數(shù)據(jù)做決策,但是現(xiàn)在公司的數(shù)據(jù)基本上不存儲在本地Excel 表中,而是存儲在數(shù)據(jù)庫中,想要從數(shù)據(jù)庫中獲取數(shù)據(jù)就需要使用SQL,所以熟練使用SQL 成了數(shù)據(jù)相關(guān)從業(yè)者入職的必要條件。本書的所有代碼和函數(shù)均以MySQL 8.0 為主。
(掃碼了解本書詳情)
如果喜歡本文
歡迎 在看丨留言丨分享至朋友圈 三連
熱文推薦
▼點擊閱讀原文,獲取本書詳情~
