SQL182 连续两次作答试卷的最大时间窗
描述
现有试卷作答记录表exam_record(uid用户ID, exam_id试卷ID, start_time开始作答时间, submit_time交卷时间, score得分):
id | uid | exam_id | start_time | submit_time | score |
1 | 1006 | 9003 | 2021-09-07 10:01:01 | 2021-09-07 10:21:02 | 84 |
2 | 1006 | 9001 | 2021-09-01 12:11:01 | 2021-09-01 12:31:01 | 89 |
3 | 1006 | 9002 | 2021-09-06 10:01:01 | 2021-09-06 10:21:01 | 81 |
4 | 1005 | 9002 | 2021-09-05 10:01:01 | 2021-09-05 10:21:01 | 81 |
5 | 1005 | 9001 | 2021-09-05 10:31:01 | 2021-09-05 10:51:01 | 81 |
请计算在2021年至少有两天作答过试卷的人中,计算该年连续两次作答试卷的最大时间窗days_window,那么根据该年的历史规律他在days_window天里平均会做多少套试卷,按最大时间窗和平均做答试卷套数倒序排序。由示例数据结果输出如下:
uid | days_window | avg_exam_cnt |
1006 | 6 | 2.57 |
解释:用户1006分别在20210901、20210906、20210907作答过3次试卷,连续两次作答最大时间窗为6天(1号到6号),他1号到7号这7天里共做了3张试卷,平均每天3/7=0.428571张,那么6天里平均会做0.428571*6=2.57张试卷(保留两位小数);
用户1005在20210905做了两张试卷,但是只有一天的作答记录,过滤掉。
WITH t2 AS (
SELECT uid,COUNT(start_time) total, -- 用户2021年作答的次数DATEDIFF(MAX(start_time),MIN(start_time))+1 diff_time, -- 头尾作答时间窗 MAX(DATEDIFF(next_time,start_time))+1 days_window -- 最大间隔天数
FROM (SELECT uid,start_time,LEAD(start_time,1)OVER(PARTITION BY uid ORDER BY start_time) AS next_time -- 第二次作答时间FROM exam_recordWHERE YEAR(start_time)=2021 -- 2021年的数据) t1
GROUP BY uid
)
SELECT uid,days_window,ROUND(total* days_window/diff_time,2) avg_exam_cnt
FROM t2
WHERE diff_time>1
ORDER BY days_window DESC,avg_exam_cnt DESC;
🔑 关键代码 & 逐行解释
1. 筛选年份:锁定 2021 年
WHERE YEAR(start_time) = 2021
- ✅ 只保留 2021 年的答题记录
- 💡
YEAR()
提取年份,常用于时间分析
2. LEAD()
:找“下一次”答题时间(核心!)
LEAD(start_time, 1) OVER (PARTITION BY uid ORDER BY start_time) AS next_time
- ✅
LEAD(列, n)
:取后面第n
行的值 - ✅
PARTITION BY uid
:按用户分组 - ✅
ORDER BY start_time
:按时间排序 - 📌 作用:让每一行都知道“我下次什么时候答题”
- ⚠️ 最后一次答题的
next_time
是NULL
示例:
start_time next_time 2021-01-01 2021-01-03 2021-01-03 2021-01-08 2021-01-08 NULL
3. 计算最大间隔天数(含首日)
MAX(DATEDIFF(next_time, start_time)) + 1 AS days_window
- ✅
DATEDIFF(后, 前)
:两个日期相差多少天 - ✅
+1
:因为“1号到3号”是2天差,但实际跨了3天(1、2、3),所以加1 - 📌
days_window
= 用户最长一次“隔了几天才答题”
4. 计算总活跃天数(首尾跨度)
DATEDIFF(MAX(start_time), MIN(start_time)) + 1 AS diff_time
- ✅ 从第一次答题到最后一次,总共经历了多少天(含首尾)
- 📌 比如:1月1日到1月8日 →
diff_time = 8
5. 计算总答题次数
COUNT(start_time) AS total
- ✅ 统计每个用户答题多少次
- 💡 注意:
COUNT
不统计NULL
,所以next_time
的NULL
不影响
6. 最终公式:调整后的日均答题估算
ROUND(total * days_window / diff_time, 2) AS avg_exam_cnt
- ✅ 公式含义:
“如果用户每次都像他最懒的那次一样间隔,那他平均每天答多少题?”
- 💡 本质是一个归一化的活跃度评分
- 📌
ROUND(..., 2)
:保留两位小数
7. 过滤条件:只看跨天用户
WHERE diff_time > 1
- ✅ 排除那些只在同一天答题的用户
- 💡 因为他们没有“间隔”可分析,行为太单一
8. 排序逻辑
ORDER BY days_window DESC, avg_exam_cnt DESC
- ✅ 先看谁“偷懒最久”(
days_window
大) - ✅ 再看谁在这种节奏下答得更多(
avg_exam_cnt
高)
🎯 输出字段总结
字段 | 含义 |
---|---|
uid | 用户ID |
days_window | 最大答题间隔天数(含首日) |
avg_exam_cnt | 调整后的日均答题频率(评分) |
✅ 复习时重点记这几点
LEAD()
是关键:用来计算“下一次时间”+1
别忘了:DATEDIFF
要 +1 才是实际跨度天数days_window
= 最大间隔,反映“最长偷懒时间”diff_time
= 首尾跨度,反映总活跃期- 公式
total × days_window / diff_time
是一个“保守估计”的活跃度 WHERE diff_time > 1
是为了排除单日用户
📌 一句话总结:
用 LEAD
找下一次时间 → 算最大间隔 → 结合总次数和总天数 → 得出一个反映用户“断续学习”行为的评分。