SQL题目分析之计算用户的平均次日留存率

 更新时间:2026年04月04日 08:42:53   作者:努力学代码的小two  
留存率是指在特定时间段内,仍然继续使用某项产品或服务的用户占用户总数的百分比,这篇文章主要介绍了SQL题目分析之计算用户的平均次日留存率的相关资料,需要的朋友可以参考下

描述

题目:现在运营想要查看用户在某天刷题后第二天还会再来刷题的留存率。请你取出相应数据。

示例:question_practice_detail

iddevice_idquestion_idresultdate
12138111wrong2021-05-03
23214112wrong2021-05-09
33214113wrong2021-06-15
46543111right2021-08-13
52315115right2021-08-13
62315116right2021-08-14
72315117wrong2021-08-15
83214112wrong2021-05-09
93214113wrong2021-08-15
106543111right2021-08-13
112315115right2021-08-13
122315116right2021-08-14
132315117wrong2021-08-15
143214112wrong2021-08-16
153214113wrong2021-08-18
166543111right2021-08-13

根据示例,你的查询应返回以下结果:

avg_ret
0.3000

题目分析

需求:计算用户在某天刷题后,第二天还会再来刷题的留存率

什么是次日留存率?次日留存率 = (第一天刷题的用户中,第二天也来刷题的用户数) / (第一天刷题的总用户数)

示例解读:根据示例数据,最终的平均留存率是 0.3000,即 30%。这意味着,在所有某天刷过题的用户中,平均有 30% 的用户会在第二天继续刷题。

关键点

  1. 用户标识:我们通过 device_id 来唯一标识一个用户。
  2. 行为日期:我们关心的是用户刷题的日期 (date)。
  3. 去重:一个用户在同一天可能刷了多道题,但我们只关心他 “是否来过”,所以需要对 (device_id, date) 进行去重。
  4. 关联:我们需要将用户第一天的行为和第二天的行为关联起来,以判断他是否留存。

解题思路

要计算留存率,我们需要明确两个集合:A. 某日活跃用户集合:在某天 date1 刷过题的用户。B. 次日留存用户集合:在集合 A 中的用户,并且在 date1 的第二天(date1 + 1天)也刷过题。

留存率就是 |B| / |A|

直接计算比较复杂,我们可以换个角度,为每一条 “某日刷题记录” 匹配一条 “次日刷题记录”,然后通过计数来求比率。

  1. 构建用户每日刷题的唯一记录:首先,我们需要一个只包含 (device_id, date) 唯一组合的数据集。
  2. 自连接匹配次日记录:将这个唯一记录数据集与自身进行左连接(LEFT JOIN)。连接条件是:
    • A.device_id = B.device_id (同一个用户)
    • B.date = A.date + INTERVAL 1 DAY (B 表的日期是 A 表日期的第二天)
  3. 计算留存率
    • 左连接的好处是,如果一个用户在第二天没有来,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_iddate
    21382021-05-03
    32142021-05-09
    32142021-06-15
    65432021-08-13
    23152021-08-13
    23152021-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_iddate1date2
    21382021-05-03NULL-- 2138 在 5 月 4 日没有来
    32142021-05-09NULL-- 3214 在 5 月 10 日没有来
    23152021-08-132021-08-14-- 2315 在 8 月 14 日来了
    23152021-08-142021-08-15-- 2315 在 8 月 15 日来了
    23152021-08-15NULL-- 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) 是 3count(date1) 是 10,那么结果就是 3/10 = 0.3

总结

这道题的解法非常巧妙地运用了“自连接”“左连接”的技巧。

  1. 自连接:通过将一个去重后的 “用户 - 日期” 表与自身连接,我们能够在一条记录里同时看到一个用户在两天的行为。
  2. 左连接:通过左连接,我们确保了不会丢失任何一个 “第一天” 的用户行为记录,同时用 NULL 值清晰地标记出了哪些用户没有在 “第二天” 回来。
  3. 计数与除法:最后,利用 COUNT() 函数对非空值的计数特性,轻松地计算出了分子(留存数)和分母(总活跃数),从而得到了最终的留存率。

这种模式在用户行为分析中非常常见,例如计算 7 日留存、月留存等,核心思想都是类似的。

到此这篇关于SQL题目分析之计算用户的平均次日留存率的文章就介绍到这了,更多相关SQL计算用户平均次日留存率内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • MySQL正则表达式匹配查询(含实例)

    MySQL正则表达式匹配查询(含实例)

    MySQL中正式表达式通常被用来检索或替换符合某个模式的文本内容,根据指定的匹配模式匹配文中符合要求的特殊字符串,下面这篇文章主要给大家介绍了关于MySQL正则表达式匹配查询的相关资料,需要的朋友可以参考下
    2022-09-09
  • windows 安装解压版 mysql5.7.28 winx64的详细教程

    windows 安装解压版 mysql5.7.28 winx64的详细教程

    这篇文章主要介绍了windows 安装解压版 mysql5.7.28 winx64的详细教程,本文通过图文并茂的形式给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-12-12
  • win2008 R2 WEB环境配置之MYSQL 5.6.22安装版安装配置方法

    win2008 R2 WEB环境配置之MYSQL 5.6.22安装版安装配置方法

    这篇文章主要介绍了win2008 R2 WEB环境配置之MYSQL 5.6.22安装版安装配置方法,需要的朋友可以参考下
    2016-06-06
  • Mysql通过explain分析定位数据库性能问题

    Mysql通过explain分析定位数据库性能问题

    这篇文章主要介绍了Mysql通过explain分析定位数据库性能问题,明确SQL在Mysql中实际的执行过程是怎样的,如果查询字段没有索引则增加索引,如果有索引就要分析为什么没有用到索引,本文详细讲解,需要的朋友可以参考下
    2023-01-01
  • CentOS7卸载MySQL5.7的方法步骤

    CentOS7卸载MySQL5.7的方法步骤

    这篇文章主要介绍了CentOS7卸载MySQL5.7的方法步骤,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-07-07
  • mysql中mvcc的具体使用

    mysql中mvcc的具体使用

    MVCC多版本并发控制是一种并发控制的方法,一般在数据库管理系统中,实现对数据库的并发访问,本文主要介绍了mysql中mvcc的具体使用,感兴趣的可以了解一下
    2024-09-09
  • linux下mysql乱码问题的解决方案

    linux下mysql乱码问题的解决方案

    今天小编就为大家分享一篇关于linux下mysql乱码问题的解决方案,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧
    2019-03-03
  • Mysql数据库按时间点恢复实战记录

    Mysql数据库按时间点恢复实战记录

    如果客户在某时间节点由于误操作,导致数据丢失,RDS管控服务是如何进行恢复的呢?通过Mysql数据库按时间点恢复该如何操作呢,感兴趣的朋友跟随小编一起看看吧
    2021-06-06
  • MySQL下载安装及完美卸载的详细过程

    MySQL下载安装及完美卸载的详细过程

    MySQL的安装卸载问题一直是一个头疼的问题,所以想着以一篇文章来搞定这个问题,这篇文章主要给大家介绍了关于MySQL下载安装及完美卸载的相关资料,需要的朋友可以参考下
    2022-08-08
  • mysql之如何把查询的结果保存到新表

    mysql之如何把查询的结果保存到新表

    这篇文章主要介绍了mysql之如何把查询的结果保存到新表问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-07-07

最新评论