SQL188 每月及截止当月的答题情况

一、题目描述

现有试卷作答记录表 exam_record,字段如下:

现有试卷作答记录表exam_record(uid用户ID, exam_id试卷ID, start_time开始作答时间, submit_time交卷时间, score得分):

|----|------|---------|---------------------|---------------------|--------|
| id | uid | exam_id | start_time | submit_time | score |
| 1 | 1001 | 9001 | 2020-01-01 09:01:01 | 2020-01-01 09:21:59 | 90 |
| 2 | 1002 | 9001 | 2020-01-20 10:01:01 | 2020-01-20 10:10:01 | 89 |
| 3 | 1002 | 9001 | 2020-02-01 12:11:01 | 2020-02-01 12:31:01 | 83 |
| 4 | 1003 | 9001 | 2020-03-01 19:01:01 | 2020-03-01 19:30:01 | 75 |
| 5 | 1004 | 9001 | 2020-03-01 12:01:01 | 2020-03-01 12:11:01 | 60 |
| 6 | 1003 | 9001 | 2020-03-01 12:01:01 | 2020-03-01 12:41:01 | 90 |
| 7 | 1002 | 9001 | 2020-05-02 19:01:01 | 2020-05-02 19:32:00 | 90 |
| 8 | 1001 | 9002 | 2020-01-02 19:01:01 | 2020-01-02 19:59:01 | 69 |
| 9 | 1004 | 9002 | 2020-02-02 12:01:01 | 2020-02-02 12:20:01 | 99 |
| 10 | 1003 | 9002 | 2020-02-02 12:01:01 | 2020-02-02 12:31:01 | 68 |
| 11 | 1001 | 9002 | 2020-01-02 19:01:01 | 2020-02-02 12:43:01 | 81 |
| 12 | 1001 | 9002 | 2020-03-02 12:11:01 | (NULL) | (NULL) |

请输出自从有用户作答记录以来,每月的试卷作答记录中月活用户数、新增用户数、截止当月的单月最大新增用户数、截止当月的累积用户数。结果按月份升序输出。

  • 新增用户:首次登录的用户,也就是把用户最早登录的那天定义为首次登录日期。
  • 截止当月的单月最大新增用户数:按照月份依次对比每个月的新增用户数的大小取大值
  • 截止当月的累积用户数:按照月份依次累加新增用户数

需求字段:

  • start_month :月份
  • mau :月活用户数
  • month_add_uv :新增用户数
  • max_month_add_uv:截止当月的单月最大新增用户数
  • cum_sum_uv:截止当月的累积用户数

由示例数据结果输出如下:

|-------------|-----|--------------|------------------|------------|
| start_month | mau | month_add_uv | max_month_add_uv | cum_sum_uv |
| 202001 | 2 | 2 | 2 | 2 |
| 202002 | 4 | 2 | 2 | 4 |
| 202003 | 3 | 0 | 2 | 4 |
| 202005 | 1 | 0 | 2 | 4 |

|--------|------|------|------|------|
| month | 1001 | 1002 | 1003 | 1004 |
| 202001 | 1 | 1 | | |
| 202002 | 1 | 1 | 1 | 1 |
| 202003 | 1 | | 1 | 1 |
| 202005 | | 1 | | |

由上述矩阵可以看出,2020年1月有2个用户活跃(mau=2),当月新增用户数为2;

2020年2月有4个用户活跃,当月新增用户数为2,最大单月新增用户数为2,当前累积用户数为4。


二、核心难点解析

难点1:什么是"新增用户"?

新增 ≠ 当月活跃

新增 = 该用户的第一次作答记录所在的月份

必须先识别每个用户的"首次行为",不能直接对当月数据去重。


难点2:如何标记"首次行为"?

正确方法:使用窗口函数 ROW_NUMBER()

复制代码
ROW_NUMBER() OVER (PARTITION BY uid ORDER BY start_time) AS rn
  • PARTITION BY uid:按用户分组
  • ORDER BY start_time:按时间排序
  • rn = 1:表示这是该用户的第一次作答
  • rn > 1:老用户回访

关键洞察: rn = 1 的记录,就是"新增用户"的发生时刻!


三、最终简洁版 SQL(推荐写法)

复制代码
-- 第一步:给每条记录标记是否是用户首次作答
WITH user_behavior AS (
  SELECT
    uid,
    start_time,
    ROW_NUMBER() OVER (PARTITION BY uid ORDER BY start_time) AS rn
  FROM exam_record
)

-- 第二步:按月份统计所有指标
SELECT
  DATE_FORMAT(start_time, '%Y%m') AS start_month,           -- 月份
  COUNT(DISTINCT uid) AS mau,                              -- 月活用户数
  SUM(CASE WHEN rn = 1 THEN 1 ELSE 0 END) AS month_add_uv, -- 当月新增用户数
  MAX(SUM(CASE WHEN rn = 1 THEN 1 ELSE 0 END)) 
    OVER (ORDER BY DATE_FORMAT(start_time, '%Y%m')) AS max_month_add_uv, -- 截止当月最大新增
  SUM(SUM(CASE WHEN rn = 1 THEN 1 ELSE 0 END)) 
    OVER (ORDER BY DATE_FORMAT(start_time, '%Y%m')) AS cum_sum_uv        -- 截止当月累积用户数
FROM user_behavior
GROUP BY DATE_FORMAT(start_time, '%Y%m')  -- 按月分组
ORDER BY start_month;

🔍 四、代码逐行解析

代码 作用 为什么这样写?
WITH user_behavior AS (...) 创建临时表,提前计算 rn 让主查询更清晰,逻辑分层
ROW_NUMBER() ... AS rn 标记用户首次行为 rn=1 就是"新增"
COUNT(DISTINCT uid) 统计当月活跃的去重用户数 标准 mau 计算
SUM(CASE WHEN rn=1 THEN 1 ELSE 0 END) 统计当月有多少用户的"第一次"发生 这就是"当月新增用户数"
MAX(SUM(...)) OVER(...) 对"每月新增数"取历史最大值 MAX() OVER(ORDER BY 月)
SUM(SUM(...)) OVER(...) 对"每月新增数"做累加 SUM() OVER(ORDER BY 月)

五、核心思想总结

1. "新增用户"问题的通用解法

"先标记,再统计"

复制代码
-- 万能模板
ROW_NUMBER() OVER (PARTITION BY uid ORDER BY 时间) AS rn

然后用 CASE WHEN rn = 1 THEN 1 ELSE 0 END 打标。


2. 窗口函数嵌套聚合函数

复制代码
MAX(SUM(...)) OVER (ORDER BY ...)
  • 外层 SUM(...):先算出每月的值
  • 内层 MAX/OVER:再对这些值做累积或取最大
  • 适用场景: 累积、历史最大、移动平均等

3. 为什么不能用"总用户 - 上月"?

  • 因为用户可能流失、回流,逻辑不严谨
  • "新增"是基于用户生命周期的,必须从源头识别
相关推荐
想摆烂的不会研究的研究生40 分钟前
每日八股——Redis(1)
数据库·经验分享·redis·后端·缓存
码熔burning1 小时前
MySQL 8.0 新特性爆笑盘点:从青铜到王者的骚操作都在这儿了!(万字详解,建议收藏)
数据库·mysql
猫头虎1 小时前
2025最新OpenEuler系统安装MySQL的详细教程
linux·服务器·数据库·sql·mysql·macos·openeuler
哈库纳玛塔塔1 小时前
放弃 MyBatis,拥抱新一代 Java 数据访问库
java·开发语言·数据库·mybatis·orm·dbvisitor
@LetsTGBot搜索引擎机器人3 小时前
2025 Telegram 最新免费社工库机器人(LetsTG可[特殊字符])搭建指南(含 Python 脚本)
数据库·搜索引擎·机器人·开源·全文检索·facebook·twitter
计算机毕设VX:Fegn08953 小时前
计算机毕业设计|基于springboot + vue动物园管理系统(源码+数据库+文档)
数据库·vue.js·spring boot·后端·课程设计
冉冰学姐4 小时前
SSM校园排球联赛管理系统y513u(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面
数据库·ssm 框架应用·开题报告、
Tony Bai4 小时前
【分布式系统】03 复制(上):“权威中心”的秩序 —— 主从架构、一致性与权衡
大数据·数据库·分布式·架构
wb043072015 小时前
SQL工坊不只是一个ORM框架
数据库·sql
至善迎风5 小时前
Redis完全指南:从诞生到实战
数据库·redis·缓存