SQL题目分析之计算用户的平均次日留存率
作者:努力学代码的小two
留存率是指在特定时间段内,仍然继续使用某项产品或服务的用户占用户总数的百分比,这篇文章主要介绍了SQL题目分析之计算用户的平均次日留存率的相关资料,需要的朋友可以参考下
描述
题目:现在运营想要查看用户在某天刷题后第二天还会再来刷题的留存率。请你取出相应数据。
示例:question_practice_detail
| id | device_id | question_id | result | date |
| 1 | 2138 | 111 | wrong | 2021-05-03 |
| 2 | 3214 | 112 | wrong | 2021-05-09 |
| 3 | 3214 | 113 | wrong | 2021-06-15 |
| 4 | 6543 | 111 | right | 2021-08-13 |
| 5 | 2315 | 115 | right | 2021-08-13 |
| 6 | 2315 | 116 | right | 2021-08-14 |
| 7 | 2315 | 117 | wrong | 2021-08-15 |
| 8 | 3214 | 112 | wrong | 2021-05-09 |
| 9 | 3214 | 113 | wrong | 2021-08-15 |
| 10 | 6543 | 111 | right | 2021-08-13 |
| 11 | 2315 | 115 | right | 2021-08-13 |
| 12 | 2315 | 116 | right | 2021-08-14 |
| 13 | 2315 | 117 | wrong | 2021-08-15 |
| 14 | 3214 | 112 | wrong | 2021-08-16 |
| 15 | 3214 | 113 | wrong | 2021-08-18 |
| 16 | 6543 | 111 | right | 2021-08-13 |
根据示例,你的查询应返回以下结果:
| avg_ret |
| 0.3000 |
题目分析
需求:计算用户在某天刷题后,第二天还会再来刷题的留存率。
什么是次日留存率?次日留存率 = (第一天刷题的用户中,第二天也来刷题的用户数) / (第一天刷题的总用户数)
示例解读:根据示例数据,最终的平均留存率是 0.3000,即 30%。这意味着,在所有某天刷过题的用户中,平均有 30% 的用户会在第二天继续刷题。
关键点:
- 用户标识:我们通过
device_id来唯一标识一个用户。 - 行为日期:我们关心的是用户刷题的日期 (
date)。 - 去重:一个用户在同一天可能刷了多道题,但我们只关心他 “是否来过”,所以需要对
(device_id, date)进行去重。 - 关联:我们需要将用户第一天的行为和第二天的行为关联起来,以判断他是否留存。
解题思路
要计算留存率,我们需要明确两个集合:A. 某日活跃用户集合:在某天 date1 刷过题的用户。B. 次日留存用户集合:在集合 A 中的用户,并且在 date1 的第二天(date1 + 1天)也刷过题。
留存率就是 |B| / |A|。
直接计算比较复杂,我们可以换个角度,为每一条 “某日刷题记录” 匹配一条 “次日刷题记录”,然后通过计数来求比率。
- 构建用户每日刷题的唯一记录:首先,我们需要一个只包含
(device_id, date)唯一组合的数据集。 - 自连接匹配次日记录:将这个唯一记录数据集与自身进行左连接(
LEFT JOIN)。连接条件是:A.device_id = B.device_id(同一个用户)B.date = A.date + INTERVAL 1 DAY(B 表的日期是 A 表日期的第二天)
- 计算留存率:
- 左连接的好处是,如果一个用户在第二天没有来,
B.date字段会是NULL。 - 因此,
COUNT(B.date)就等于 “第二天也来的用户数”。 COUNT(A.date)就等于 “第一天来的总用户数”。- 两者相除,就得到了我们想要的平均次日留存率。
- 左连接的好处是,如果一个用户在第二天没有来,
分步解析最终代码
-- 外层查询:计算平均次日留存率
select count(date2)/count(date1) as avg_ret
from(
-- 子查询 A: 为每个用户的每日刷题记录匹配次日是否也刷题
select distinct
qpd.device_id,
qpd.date as date1, -- 用户当天刷题的日期
uniq_id_date.date as date2 -- 用户次日是否刷题的日期(可能为NULL)
from
question_practice_detail as qpd
left join(
-- 子查询 B: 获取所有用户所有刷题日期的唯一记录
select distinct device_id, date
from question_practice_detail
)as uniq_id_date
-- 左连接条件
on qpd.device_id = uniq_id_date.device_id -- 同一个用户
and date_add(qpd.date, interval 1 day) = uniq_id_date.date -- 次日
)as id_last_next_code;1. 子查询 B (uniq_id_date)
select distinct device_id, date from question_practice_detail
- 目标:创建一个 “用户 - 日期” 的唯一映射表。
- 作用:这个子查询是整个逻辑的基石。它确保了我们处理的每一条记录都代表一个用户在某一天的一次独立访问行为,无论他当天刷了多少道题。
- 结果示例(基于题目数据):
device_id date 2138 2021-05-03 3214 2021-05-09 3214 2021-06-15 6543 2021-08-13 2315 2021-08-13 2315 2021-08-14 ... ...
2. 子查询 A (id_last_next_code)
select distinct
qpd.device_id,
qpd.date as date1,
uniq_id_date.date as date2
from
question_practice_detail as qpd
left join
uniq_id_date
on
qpd.device_id = uniq_id_date.device_id
and date_add(qpd.date, interval 1 day) = uniq_id_date.date- 目标:这是核心的匹配步骤。对于原始表中的每一条刷题记录(这里用
qpd别名),我们都尝试在uniq_id_date表中找到该用户次日的刷题记录。 FROM question_practice_detail as qpd:我们从原始表开始,而不是从去重后的表开始。这是为了确保我们考虑到所有的 “刷题日”,即使一个用户一天刷了多道题,distinct会保证最终每(device_id, date1)只留下一条记录。LEFT JOIN uniq_id_date:使用左连接至关重要。它会保留qpd表中的所有记录。如果在uniq_id_date中找到了匹配的次日记录,date2就会有值;如果没找到,date2就会是NULL。date_add(qpd.date, interval 1 day):这是一个日期函数,用于计算qpd.date的第二天。select distinct ...:再次使用distinct是为了确保对于同一个用户在同一天的多次刷题行为,我们在最终的派生表中只保留一条(device_id, date1, date2)记录。- 结果示例:
device_id date1 date2 2138 2021-05-03 NULL -- 2138 在 5 月 4 日没有来 3214 2021-05-09 NULL -- 3214 在 5 月 10 日没有来 2315 2021-08-13 2021-08-14 -- 2315 在 8 月 14 日来了 2315 2021-08-14 2021-08-15 -- 2315 在 8 月 15 日来了 2315 2021-08-15 NULL -- 2315 在 8 月 16 日没有来 ... ... ...
3. 外层查询
select count(date2)/count(date1) as avg_ret from id_last_next_code;
- 目标:计算最终的平均留存率。
count(date2):计算id_last_next_code表中date2字段不为NULL的行数。这正好等于 “次日留存的用户行为次数”。count(date1):计算id_last_next_code表中date1字段不为NULL的行数。这正好等于 “第一天的总用户行为次数”。count(date2)/count(date1):两者相除,得到的就是平均次日留存率。- 在我们的示例中,如果
count(date2)是3,count(date1)是10,那么结果就是3/10 = 0.3。
总结
这道题的解法非常巧妙地运用了“自连接”和“左连接”的技巧。
- 自连接:通过将一个去重后的 “用户 - 日期” 表与自身连接,我们能够在一条记录里同时看到一个用户在两天的行为。
- 左连接:通过左连接,我们确保了不会丢失任何一个 “第一天” 的用户行为记录,同时用
NULL值清晰地标记出了哪些用户没有在 “第二天” 回来。 - 计数与除法:最后,利用
COUNT()函数对非空值的计数特性,轻松地计算出了分子(留存数)和分母(总活跃数),从而得到了最终的留存率。
这种模式在用户行为分析中非常常见,例如计算 7 日留存、月留存等,核心思想都是类似的。
到此这篇关于SQL题目分析之计算用户的平均次日留存率的文章就介绍到这了,更多相关SQL计算用户平均次日留存率内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
