题解 | #2021年11月每天新用户的次日留存率#

2021年11月每天新用户的次日留存率

https://www.nowcoder.com/practice/1fc0e75f07434ef5ba4f1fb2aa83a450

做一次忘一次,这次终于整理好思路啦!

先来看看知识点:

  • 滑动窗口函数:min(字段1) over(partition by 字段1 order by 字段2 rows between a and b

a取值:unbounded preceding(划分排序后前面所有行)/ n preceding / current row

b取值:unbounded following / n following/ 0 following(=current row)

注意:1️⃣rows between n preceding and current row 可以简写为:rows n preceding

2️⃣当不对rows进行限制时,默认的是rows unbouded preceding

  • 时间序列窗口函数 :lead(字段1,n[,default]) over(partition by 字段1 order by 字段2

字段1一般是时间,表示将字段1向后移动n行得到的值;default表示当值为空时设定的默认值,可省略。

lead窗口函数常用于解决连续登录问题、次日留存问题,非常重要!

还有一个相对的函数:(表示向前移动n行,其他不变)

lag(字段1,n[,default]) over(partition by 字段1 order by 字段2

看完下面的讲解后,可以再来看看能回答下面两个问题吗?

  • 为什么where date_format(dt,"%Y%m") = '202111'不写在临时表t2中?

因为写在t2中,如果一个用户在11月以前首次登录过一次,min(dt) over(partition by uid)取到的日期依旧是11月的,也就是错把旧用户当成了新用户。

  • if(datediff(lead(dt,1) over(partition by uid order by dt) 1,0) as if_next_retention 这样写是否可以运行通过?

是可以运行通过的,但这并不说明代码严谨,只是牛客的测试用例较少了,也是我第一次犯的错误。

有下面简单的情况:

101 | 2021-11-01

101 | 2021-11-02

102 | 2021-11-02

101 | 2021-11-03

此时三行对应的if_newif_next_retention如下:

101 | 2021-11-01 | 1 | 1

101 | 2021-11-02 | 0 | 1

102 | 2021-11-02 | 1 | 0

101 | 2021-11-03 | 1 | 0

但是101不是2021-11-02次日留存的新用户,就会出错。

整体思路如下:

step1:取出用户的活跃时间表,代码如下:

with t1 as(

select uid, date(in_time) as dt

from tb_user_log

union

select uid, date(out_time)

from tb_user_log)

解释:

1️⃣当in_time 和 out_time跨天时,都算做活跃日,因此需要合并起来。

2️⃣用union自动去重,使得t1表(uid,dt)是唯一的

3️⃣union后的select语句中的date(out_time)不需要别名,直接默认union前的select对应的。

step2:判断用户是否新用户以及是否次日留存。

with t2 as(

select uid

,dt

,if(dt =min(dt) over(partition by uid),1,0) as if_new

,if(datediff(lead(dt,1) over(partition by uid order by dt) and dt =min(dt) over(partition by uid), dt)=1,1,0) as if_next_retention

from t1)

解释:

1️⃣if_new字段:min(dt) over(partition by uid)取出每个用户的最小登录日期,判断它与dt是否相同,相同的话标记为1,否则为0;

2️⃣if_next_retention字段:lead(dt,1) over(partition by uid order by dt) 取出每个用户下一次登录日期, 用datediff函数判断出它 与dt的差是否为1,是1的话就说明次日活跃(但非新用户次日活跃),因此日期差等于1且dt =min(dt) over(partition by uid), dt)=1 才说明是新用户次日活跃,这样标记为1,否则标记为0

3️⃣if_new=1 说明是当日新用户;if_next_retention=1说明是次日留存的新用户。

step3:计算次日留存率

select dt, round(sum(if_next_retention)/sum(if_new),2) as as uv_left_rate

from t2

where date_format(dt,"%Y%m") = '202111'

group by dt

having sum(if_new)>0

order by dt

解释:

1️⃣对dt分组,sum(if_new)是当天活跃新用户,sum(if_next_retention)是次日留存新用户,作比即可。

2️⃣where限制住11月数据

3️⃣having sum(if_new)>0是保证当天没有新用户就不输出了。

完整代码如下:

select dt, round(sum(if_next_retention)/sum(if_new),2) as uv_left_rate

from(

select uid,dt

,if(dt =min(dt) over(partition by uid),1,0) as if_new

,if(datediff(lead(dt,1) over(partition by uid order by dt),dt)=1 and dt =min(dt) over(partition by uid),1,0) as if_next_retention

from(

select uid,

date(in_time) as dt

from tb_user_log

union

select uid,

date(out_time)

from tb_user_log

) as t1

) as t2

where date_format(dt,"%Y%m") = '202111'

group by dt

having sum(if_new)>0

order by dt

全部评论
写的好清晰呀!
1 回复 分享
发布于 2024-08-07 20:41 天津

相关推荐

面试官全程关摄像头1.自我介绍一下2.React和Vue哪个更熟悉一点3.你在之前那段实习经历中有没有什么技术性的突破(我只是实习了44天工作28天,我把我能说的都说了)4.你封装的哪个表单组件支不支持动态传值5.自己在实习阶段Vue3项目封装过hook吗6.hook有什么作用7.Vue2和Vue3的响应式区别(我说一个是proxy是拦截所有的底层操作,Object.defineProperty本身就是一个底层操作,有些东西拦截不了,比如数组的一些操作还有等等,面试官就说实在要拦截能不能拦截????我心想肯定不行呀,他的底层机制就不允许吧)8.pinia和vuex的区别(这个回答不出来是我太久没用了)9.pinia和zustand的区别,怎么选(直接给我干懵了)(我说react能用pinia吗  他说要用的话也可以)10.渲染一万条数据,怎么解决页面卡顿问题(我说分页、监听滚轮动态加载,纯数据展示好像还可以用canvas画)(估计是没说虚拟表单,感觉不满意)11.type和interface的区别12.ts的泛型有哪些作用(我就说了一个结构相同但是类型不同的时候可以用,比如请求响应的接口,每次的data不同,这里能用一个泛型,他问我还有什么)13.你项目用的是React,如果让你再写一遍你会选择什么14.pnpm、npm、yarn的区别15.dependencies和devdependencies的区别总而言之太久没面试了,上一段实习的面试js问了很多。结果这次js一点没问,网络方面也没考,表现得很一般,但是知道自己的问题了  好好准备,等待明天的影石360和周四的腾讯了  加油!!!
解zj:大三的第一段面试居然是这样的结局
查看15道真题和解析
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务