1. 程式人生 > 其它 >SQL之留存率的題目

SQL之留存率的題目

關於留存率的SQL語句,之前看到猴子分析那裡給過一個思路,是用timestampdiff函式來求,而且有一個模板,可以統一求次日留存率、三日留存率、七日留存率之類的,但是在牛客網刷題也遇到一些留存率分析的題目,發現試圖套模板出了問題,因此這裡梳理總結一下思路。

先看資料如下:表:login,欄位分別有id、user_id、client_id、date,分別表示序號、使用者id、裝置號、登入日期

然後要求輸出格式如下:

一、猴子分析的求留存率思路:

1.求出每天的活躍使用者數:

select date,count(distinct user_id) as 活躍使用者數
from login
group by date

顯示結果如下:

2.求出次日留存數:

利用自聯結【一個表如果涉及到時間間隔,就需要用到自聯結,也就是將兩個相同的表進行聯結】,將兩個表連起來,使用timestampdiff(unit,begin,end)求出時間間隔,然後用case when 計算出時間間隔=1的使用者數量,即次日留存數。

注意:這裡猴子分析的表述有點歧義,真正的自聯結,應該是給同一張表起別名,比如loign as a,login as b,然後再在where裡面加條件,但其實猴子分析這裡提供的連線是用的外連線裡的左連線 left join,將同一張表起別名連線起來。

自連結:

select
a.user_id,a.date as d1,b.date as d2 from login as a,login as b where a.user_id = b.user_id

左連線:

select a.user_id,a.date as d1,b.date as d2
from login as a
left join login as b
on a.user_id = b.user_id

可以看出來查出來結果除了順序有點不一樣以外,其餘內容本質上是一樣的。


然後使用timestampdiff(unit,begin,end)求出時間間隔,然後用case when 計算出時間間隔=1的使用者數量,即次日留存數。

select d1,count(distinct case when day_diff = 1 then user_id else null end) as 次日留存數
from(select *,timestampdiff(day,d1,d2) as day_diff from( select a.user_id,a.date as d1,b.date as d2 from login as a left join login as b on a.user_id = b.user_id) as c) as d group by d1

3.求出次日留存率

留存率 = 新增使用者中登入使用者數/新增使用者數,所以次日留存率 = 次日留存使用者數/當日使用者活躍數

這是猴子分析中給出的公式,注意是有問題的,具體問題下面會說!

當日活躍使用者數是count(disctinct 使用者id),在上面分析次日留存數中,用次日留存使用者數/當日使用者活躍數就是次日留存率

select d1,count(distinct case when day_diff = 1 then user_id else null end) as 次日留存數,
          count(distinct case when day_diff = 1 then user_id else null end)/count(distinct user_id) as 次日留存率 
from(select *,timestampdiff(day,d1,d2) as day_diff
     from(
          select a.user_id,a.date as d1,b.date as d2
          from login as a,login as b
          where a.user_id = b.user_id) as c) as d
group by d1

但這個結果跟牛客網給出的答案是不符合的!因為2020-10-14號這天的次日留存率標準答案應該是1!這個問題就在於猴子分析給出的次日留存率公式是有問題的!次日留存率應該是次日留存使用者數/當日新增使用者數,重點就是分母應該是新增使用者數,而不是猴子分析裡給出的活躍使用者數!這種演算法會把老使用者也算進去,如果仔細觀察原始資料表login就會知道,2020-10-14號登入的使用者有兩個,分別是user_id為3和user_id為4的,但2020-10-15號登入的使用者只有user_id為4的使用者了,所以按照猴子分析的演算法2020-10-14號這天的次日留存率會是0.5。但實際上user_id為3的使用者早在2020-10-12就登入了,所以他根本不能被算在2020-10-14的新增使用者裡,他只是一個老使用者!!!

所以想真正的計算出每個日期新使用者的次日留存率必須要計算出兩張表,一張是每天的新使用者表,一張是每天的留存使用者表!下面給出牛客網的大神的解題思路:

二、牛客大神的統計新使用者留存率思路:

以集合的思想來理解,以date對整體進行劃分,劃分為之後每個集合裡需要包括每個date的新使用者數,每個date的留存使用者數,這兩部分相除組成留存率

select date,xxx
from 
group by date

基於此進一步完善我們想要的框架:

select c.date,round( count(d.user_id)/count(),3)as p
from(每天的新使用者表) as c
left join(每天的留存使用者表) as d
on c.user_id = d.user_id
group by c.date

接下來再分別寫一個新使用者表和留存使用者表,完了塞進上述結構裡去:

新使用者表,別名c

select a.date,b.user_id
from (select distinct l1.date 
      from login l1) as a
left join (select l2.user_id,min(l2.date) as f_date
           from login l2
           group by l2.user_id) as b
on a.date = b.f_date

表a:【表a存在的目的是為了把所有日期展示出來,否則只會有出現新使用者登入的12和14號,13號和15號就被遺漏了

表b:

最終查出來的每天的新使用者表(別名c)如下:

留存使用者表,別名d【留存使用者表因為涉及到時間間隔=1的問題,所以用之前猴子分析中提到的兩表連線最簡單了(自連結左連線都行)

select distinct l3.user_id
from login l3,login l4
where l3.user_id =l4.user_id
and date_add(l3.date,interval 1 day)=l4.date #這裡換成 and timestampdiff(day,l3.date,l4.date)=1 一樣的效果

最後將新使用者表c和留存使用者表d連線起來,以新使用者表c為主表,左連線留存使用者表d:

select *
from(select a.date,b.user_id
     from (select distinct l1.date
           from login as l1) as a
     left join (select user_id,min(date) as m_date
                from login as l2
                group by user_id) as b
      on a.date = b.m_date) as c
left join(select distinct l3.user_id
          from login l3,login l4
          where l3.user_id =l4.user_id
          and date_add(l3.date,interval 1 day)=l4.date) as d
on c.user_id = d.user_id

然後塞進一開始的框架裡,組成完整的SQL查詢語句:

select date,round(count(distinct d.user_id)/count(distinct c.user_id),3) as p
from(select a.date,b.user_id
     from (select distinct l1.date
           from login as l1) as a
     left join (select user_id,min(date) as m_date
                from login as l2
                group by user_id) as b
      on a.date = b.m_date) as c
left join(select distinct l3.user_id
          from login l3,login l4
          where l3.user_id =l4.user_id
          and date_add(l3.date,interval 1 day)=l4.date) as d
on c.user_id = d.user_id
group by date

如果要求結果一定要顯示為0而不是NULL的話,可以再加一個ifnull()函式,把NULL替換成0

select date,ifnull(round(count(distinct d.user_id)/count(distinct c.user_id),3),0)as p