Sql如何统计连续打卡天数
總第208篇/張俊紅
今天來解一道題面試中可能經(jīng)常會(huì)被一些面試官拿來“刁難”的題,就是《如何統(tǒng)計(jì)連續(xù)打卡天數(shù)》,當(dāng)然了這里面的打卡可以換成任意其他行為,比如連續(xù)登陸天數(shù),連續(xù)學(xué)習(xí)天數(shù),連續(xù)購買天數(shù),這里的天數(shù)也是可以換成小時(shí)或者別的時(shí)間單位的。這個(gè)問題的邏輯還是有點(diǎn)復(fù)雜,如果要是之前沒遇到過這種問題,當(dāng)場(chǎng)被問到的時(shí)候,肯定會(huì)一臉懵。
直接來看實(shí)戰(zhàn),現(xiàn)在有一張表t,這張表存儲(chǔ)了每個(gè)員工每天的打卡情況,現(xiàn)在需要統(tǒng)計(jì)截止目前每個(gè)員工的連續(xù)打卡天數(shù),表t如下表所示:
| 1 | 2020/2/1 | 1 |
| 1 | 2020/2/2 | 0 |
| 1 | 2020/2/3 | 1 |
| 1 | 2020/2/4 | 1 |
| 1 | 2020/2/5 | 0 |
| 1 | 2020/2/6 | 1 |
| 1 | 2020/2/7 | 1 |
| 1 | 2020/2/8 | 1 |
| 2 | 2020/2/1 | 1 |
| 2 | 2020/2/2 | 0 |
| 2 | 2020/2/3 | 0 |
| 2 | 2020/2/4 | 1 |
| 2 | 2020/2/5 | 1 |
| 2 | 2020/2/6 | 1 |
| 2 | 2020/2/7 | 1 |
| 2 | 2020/2/8 | 1 |
上表中uid是用戶id,tdate是日期,is_flag是記錄用戶當(dāng)天是否打卡,1為打卡,0為未打卡。
我們希望得到的結(jié)果為:
| 1 | 3 |
| 2 | 5 |
這個(gè)邏輯還是挺難想的,第一個(gè)想法就是通過前后數(shù)據(jù)偏移來實(shí)現(xiàn),就是將is_flag向前移動(dòng)一行或者向后移動(dòng)一行,然后和原來的is_flag標(biāo)簽做差,如果結(jié)果為0,說明前后兩天的值是相同的,要么都是0,要么都是1。但是還是不能夠得出我們想要的結(jié)果。
再換一種思路:如果是連續(xù)打卡,那么打卡日期與一個(gè)遞增的數(shù)字依次做差的結(jié)果值應(yīng)該是相等的,不理解這句話沒關(guān)系,看具體結(jié)果你就明白了。
我們先獲取每個(gè)用戶在這一段時(shí)間內(nèi)所有打卡的排名,是所有打卡的排名哦,利用的是窗口函數(shù)的row_number(),代碼如下:
select??uid,tdate,row_number()?over(partition?by?uid?order?by?tdate)?date_rank fromt where?is_flag=1運(yùn)行上面的代碼,可以得到如下結(jié)果:
| 1 | 2020/2/1 | 1 |
| 1 | 2020/2/3 | 2 |
| 1 | 2020/2/4 | 3 |
| 1 | 2020/2/6 | 4 |
| 1 | 2020/2/7 | 5 |
| 1 | 2020/2/8 | 6 |
| 2 | 2020/2/1 | 1 |
| 2 | 2020/2/4 | 2 |
| 2 | 2020/2/5 | 3 |
| 2 | 2020/2/6 | 4 |
| 2 | 2020/2/7 | 5 |
| 2 | 2020/2/8 | 6 |
接著再獲取每個(gè)打卡日期(tdate)中的日與其打卡日期排名(date_rank)之間的差,比如uid=1的2020/2/3的打卡日期中的3號(hào)與其排名(date_rank)2做差等于1,實(shí)現(xiàn)代碼如下:
select?uid,tdate,date_rank,(date_format(tdate,"%e")?-?date_rank)?as?day_cha from?(select??uid,tdate,row_number()?over(partition?by?uid?order?by?tdate)?date_rankfromdemo.newtablewhere?is_flag=1)t1運(yùn)行上面的代碼,最后可以得到如下結(jié)果:
| 1 | 2020/2/1 | 1 | 0 |
| 1 | 2020/2/3 | 2 | 1 |
| 1 | 2020/2/4 | 3 | 1 |
| 1 | 2020/2/6 | 4 | 2 |
| 1 | 2020/2/7 | 5 | 2 |
| 1 | 2020/2/8 | 6 | 2 |
| 2 | 2020/2/1 | 1 | 0 |
| 2 | 2020/2/4 | 2 | 2 |
| 2 | 2020/2/5 | 3 | 2 |
| 2 | 2020/2/6 | 4 | 2 |
| 2 | 2020/2/7 | 5 | 2 |
| 2 | 2020/2/8 | 6 | 2 |
看上面的結(jié)果表,有沒有看出點(diǎn)意思來,連續(xù)打卡日期的day_cha都是相等的,比如uid=1的2020/2/3和2020/2/4是連續(xù)的,他們的day_cha都是1。到這里,如果我們要獲取連續(xù)打卡天數(shù)是不是就很容易了。
不過這里面還有一個(gè)問題,就是連續(xù)打卡天數(shù)是截止目前最近的一個(gè) 連續(xù)打卡天數(shù)還是歷史堅(jiān)持最長的打卡天數(shù),這就是傳說中的口徑問題哈。雖然在我們這個(gè)例子里面,這兩種打卡天數(shù)的出來的結(jié)果是一樣的,但是有的時(shí)候會(huì)是不一樣的,比如下面這樣的例子:
| 1 | 2020/2/1 | 1 |
| 1 | 2020/2/2 | 0 |
| 1 | 2020/2/3 | 1 |
| 1 | 2020/2/4 | 1 |
| 1 | 2020/2/5 | 1 |
| 1 | 2020/2/6 | 0 |
| 1 | 2020/2/7 | 1 |
| 1 | 2020/2/8 | 1 |
上面這個(gè)例子中,最近連續(xù)打卡天數(shù)是2,歷史最長的連續(xù)打卡天數(shù)卻是3。
好了,我們繼續(xù)回到解題上,我們先獲取每個(gè)用戶歷史所有連續(xù)過得的打卡情況,實(shí)現(xiàn)代碼如下:
select?uid,day_cha,count(tdate)?flag_days from?(select?uid,tdate,date_rank,(date_format(tdate,"%e")?-?date_rank)?as?day_cha from?(select??uid,tdate,row_number()?over(partition?by?uid?order?by?tdate)?date_rankfromdemo.newtablewhere?is_flag=1)t1)t2 group?by?uid,day_cha運(yùn)行上面的代碼,得到如下結(jié)果:
| 1 | 0 | 1 |
| 1 | 1 | 2 |
| 1 | 2 | 3 |
| 2 | 0 | 1 |
| 2 | 2 | 5 |
要獲取最近的連續(xù)打卡天數(shù),我們只需要把上表中day_cha這一列最大的值對(duì)應(yīng)的flag_days取出來就可以;要獲取歷史最久的連續(xù)打卡天數(shù),我們只需要把上表中flag_days的最大值取出來就可以。直接再來個(gè)子查詢就好了。
類似的需求可能還有獲取過去連續(xù)打卡天數(shù)大于某個(gè)值得人,只需要篩選上表中的flag_days即可達(dá)到目的。只要能夠生成上面這樣每個(gè)人歷史所有連續(xù)打卡的情況表,那么大部分連續(xù)打卡相關(guān)的需求都可以通過上表來獲得。
很經(jīng)典的一道題,或者是一種業(yè)務(wù)場(chǎng)景,大家各自多多練習(xí)。
你還可以看:
講講你不知道的窗口函數(shù)
總結(jié)
以上是生活随笔為你收集整理的Sql如何统计连续打卡天数的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 松下 Lumix S5M2X 全画幅无反
- 下一篇: 讲讲什么是帕累托最优