一、题目回顾
📄 题目描述
统计每个 level=0 的用户 ,在所有 difficulty='hard' 的试卷中的:
- ✅ 平均得分(未完成 → 记为 0 分)
- ✅ 平均用时(未完成 → 记为试卷规定时长
duration)
avg_score:取整(TRUNCATE)avg_time_took:保留 1 位小数(ROUND)
📊 示例数据
| id | uid | exam_id | start_time | submit_time | score |
|---|---|---|---|---|---|
| 1 | 1001 | 9001 | 2020-01-02 09:01:01 | 2020-01-02 09:21:59 | 80 |
| 2 | 1001 | 9001 | 2021-05-02 10:01:01 | NULL | NULL |
| 4 | 1001 | 9001 | 2021-06-02 19:01:01 | 2021-06-02 19:32:00 | 20 |
examination_info 表:
| exam_id | duration | difficulty |
|---|---|---|
| 9001 | 60 | hard |
✅ 期望输出
| uid | avg_score | avg_time_took |
|---|---|---|
| 1001 | 33 | 36.7 |
🧠 二、关键解题思路(四步法)
🔹 第一步:确定数据源与过滤条件
FROM user_info ui
JOIN exam_record er ON ui.uid = er.uid
JOIN examination_info ei ON er.exam_id = ei.exam_id
WHERE ui.level = 0 AND ei.difficulty = 'hard'
✅ 只关注 level=0 用户 + 高难度试卷
🔹 第二步:处理"未完成"情况(核心!)
📌 得分处理
-
未交卷(
submit_time IS NULL)或score IS NULL→ 记为 0 分CASE
WHEN er.submit_time IS NULL OR er.score IS NULL THEN 0
ELSE er.score
END AS score
⚠️ 不能用
IFNULL(score, 0),因为有些记录submit_time是 NULL 但score不是(异常数据)
📌 用时处理
-
未交卷 → 用
ei.duration -
异常时间(
submit_time < start_time)→ 也用ei.duration -
正常交卷 → 用
TIMESTAMPDIFF(MINUTE, ...)CASE
WHEN er.submit_time IS NULL THEN ei.duration
WHEN er.submit_time < er.start_time THEN ei.duration
ELSE TIMESTAMPDIFF(MINUTE, er.start_time, er.submit_time)
END AS duration_min
💡 重点:用
MINUTE而不是/60.0,因为题目要求"20分钟",是向下取整的整数分钟
🔹 第三步:分组聚合
GROUP BY result.uid
按用户分组,计算每个用户的平均值
🔹 第四步:格式化输出
TRUNCATE(AVG(score), 0) -- 平均分取整(去小数)
ROUND(AVG(duration_min), 1) -- 平均用时保留 1 位小数
⚠️ 区别:
TRUNCATE(33.9, 0)→ 33ROUND(33.5, 0)→ 34(四舍五入,不符合"取整"要求)
✅ 三、最终正确 SQL
SELECT
result.uid,
TRUNCATE(AVG(score), 0) AS avg_score,
ROUND(AVG(duration_min), 1) AS avg_time_took
FROM (
SELECT
er.uid,
-- 未完成或无分 → 0 分
CASE
WHEN er.submit_time IS NULL OR er.score IS NULL THEN 0
ELSE er.score
END AS score,
-- 未完成或异常时间 → 用 duration;否则用实际分钟数(向下取整)
CASE
WHEN er.submit_time IS NULL THEN ei.duration
WHEN er.submit_time < er.start_time THEN ei.duration
ELSE TIMESTAMPDIFF(MINUTE, er.start_time, er.submit_time)
END AS duration_min
FROM user_info ui
JOIN exam_record er ON ui.uid = er.uid
JOIN examination_info ei ON er.exam_id = ei.exam_id
WHERE ui.level = 0 AND ei.difficulty = 'hard'
) AS result
GROUP BY result.uid;
🧮 四、结果验证(手动计算)
| 记录 | 用时(分钟) | 得分 |
|---|---|---|
| id=1 | TIMESTAMPDIFF(MINUTE, '09:01:01', '09:21:59') = 20 |
80 |
| id=2 | duration=60 |
0 |
| id=4 | TIMESTAMPDIFF(MINUTE, '19:01:01', '19:32:00') = 30 |
20 |
- 总用时:20 + 60 + 30 = 110
- 平均用时:110 / 3 = 36.666... →
ROUND(36.666, 1)= 36.7 - 总得分:80 + 0 + 20 = 100
- 平均得分:100 / 3 ≈ 33.33 →
TRUNCATE(33.33, 0)= 33
✅ 完全匹配期望输出!
🛠 五、常见陷阱与避坑指南
| 陷阱 | 错误写法 | 正确做法 |
|---|---|---|
❌ 用 /60.0 算分钟 |
TIMESTAMPDIFF(SECOND, ...)/60.0 → 20.97 |
✅ 用 TIMESTAMPDIFF(MINUTE, ...) → 20 |
❌ 用 ROUND 取整 |
ROUND(33.9, 0) → 34 |
✅ 用 TRUNCATE(..., 0) → 33 |
❌ 忽略 submit_time < start_time |
直接计算 → 负数 | ✅ 用 CASE 判断并替换为 duration |
| ❌ 隐式 JOIN(逗号) | FROM a, b, c WHERE ... |
✅ 用 JOIN ... ON 显式连接 |
| ❌ 外层冗余 JOIN | FROM exam_record JOIN (...) |
✅ 只在子查询中处理即可 |
📌 六、高频函数速查表
| 函数 | 用途 | 示例 |
|---|---|---|
TIMESTAMPDIFF(MINUTE, start, end) |
计算分钟差(向下取整) | 20 |
TIMESTAMPDIFF(SECOND, start, end)/60.0 |
精确到小数的分钟 | 20.97 |
TRUNCATE(x, 0) |
去小数取整 | 33.9 → 33 |
ROUND(x, 1) |
保留 1 位小数 | 36.66 → 36.7 |
CASE WHEN ... THEN ... ELSE ... END |
条件判断 | 处理 NULL 和异常 |
AVG() |
求平均值 | AVG(score) |
🎯 七、总结:解题模板(通用)
SELECT
result.uid,
TRUNCATE(AVG(score), 0) AS avg_score,
ROUND(AVG(duration_min), 1) AS avg_time_took
FROM (
SELECT
er.uid,
CASE WHEN [未完成条件] THEN 0 ELSE score END AS score,
CASE WHEN [未完成条件] THEN duration ELSE 实际用时 END AS duration_min
FROM user_info ui
JOIN exam_record er ON ...
JOIN examination_info ei ON ...
WHERE [业务过滤条件]
) AS result
GROUP BY result.uid;
只需替换
[条件]和[用时计算]即可复用!