题目需求:统计2021年11月每天新用户的次日留存率(保留2位小数)
条件:
1)只需要2021年11月的数据行;
2)次日留存率为【当天新增的用户数】中第二天又活跃了的用户数占比 —— 第一个坑点,只限于当天新增的用户,若用户在11-01、11-02、11-03、11-04都出现过,则只考虑11-01的留存率;
3)in_time-进入时间和out_time-离开时间跨天了,在两天里都记为该用户活跃过 —— 这是本题最大的坑点!!即,只要用户在第一天登陆,一直到第二天才退出的话,也算做该用户在次日留存下来了;
解题思路
步骤1:确认每个用户的第一次登陆的日期,因为留存率只针对该日期来计算
这一步很简单,只需要使用MIN函数找到每个用户的第一天即可:
SELECT uid, MIN(DATE(in_time)) AS first_dt
FROM tb_user_log
GROUP BY uid
步骤2:找出每个用户出现过的日期,这一步要把用户的登入、登出两个时间段所属的日期都找出来,使得那些“进入时间与离开时间跨天”的用户,也能够被识别出来
此步骤,直接使用UNION即可 —— 但是,需要提醒各位(包括我自己),UNION是会对重复行进行去重的,因此,无需担心UNION的结果会出现同一个用户有两行相同的出现日期,也无需再浪费代码去对UNION的结果进行SELECT DISTINCT
SELECT uid, DATE(in_time) AS dt
FROM tb_user_log
UNION
SELECT uid, DATE(out_time) AS dt
FROM tb_user_log
步骤3:对步骤1和2的两个表进行联结,找出来在首次登录的次日也有出现(无论是进入、还是登出)的用户,再计算次日留存率
这里需要使用“表1 LEFT OUTER JOIN 表2”来解决,分母就是表1中,首次登录日期下的用户数;分子则是表2中,次日仍有出现的用户数
SELECT t1.first_dt AS dt,
/* 留存率保留两位数 */
ROUND(COUNT(t2.uid) / COUNT(t1.uid), 2) AS uv_left_rate
FROM
(SELECT uid, MIN(DATE(in_time)) AS first_dt
FROM tb_user_log
GROUP BY uid) AS t1
LEFT OUTER JOIN
(SELECT uid, DATE(in_time) AS dt
FROM tb_user_log
UNION
SELECT uid, DATE(out_time) AS dt
FROM tb_user_log) AS t2
/* 同一个用户,在first_dt的次日也出现的联结条件如下 */
ON t1.uid = t2.uid
AND DATE_ADD(t1.first_dt, INTERVAL 1 DAY) = t2.dt
/* 只需要2021年11月的数据行,结果按日期升序 */
WHERE t1.first_dt BETWEEN '2021-11-01' AND '2021-11-30'
GROUP BY t1.first_dt
ORDER BY t1.first_dt ASC;