题目描述
现有表 exam_record,记录用户作答试卷的信息:
| 字段名 | 含义 |
|---|---|
| uid | 用户 ID |
| exam_id | 试卷 ID |
| start_time | 开始作答时间 |
| submit_time | 交卷时间(NULL 表示未完成) |
| score | 得分 |
要求:
统计有未完成状态的试卷的:
incomplete_cnt:未完成数量(即submit_time为 NULL 的记录数)incomplete_rate:未完成率(保留 3 位小数)
✅ 只输出未完成率 > 0 的试卷。
解题思路
Step 1:理解"未完成"的定义
- 一条记录只要
submit_time IS NULL,就表示未完成。 - 注意:
start_time通常非空(表示已开始作答),所以每条记录都算一次"作答尝试"。
Step 2:核心统计逻辑
- 每个
exam_id分组后:- 总作答次数 =
COUNT(*) - 已完成次数 =
COUNT(submit_time)(自动忽略 NULL) - 未完成次数 =
COUNT(*) - COUNT(submit_time) - 未完成率 =
(未完成次数) / (总次数)
- 总作答次数 =
Step 3:过滤条件
- 外层使用
WHERE incomplete_rate > 0,只保留有未完成记录的试卷。
正确代码
SELECT
exam_id,
incomplete_cnt,
incomplete_rate
FROM
(
SELECT
exam_id,
COUNT(*) - COUNT(submit_time) AS incomplete_cnt,
ROUND((COUNT(*) - COUNT(submit_time)) * 1.0 / COUNT(*), 3) AS incomplete_rate
FROM
exam_record
GROUP BY
exam_id
) AS exam_stats
WHERE
incomplete_rate > 0;
代码解析(逐行详解)
| 行号 | 代码片段 | 说明 |
|---|---|---|
| 1-3 | SELECT exam_id, incomplete_cnt, incomplete_rate |
选择最终输出字段 |
| 4 | FROM (...) AS exam_stats |
使用子查询封装聚合结果,提高可读性 |
| 6 | COUNT(*) - COUNT(submit_time) |
计算未完成数量: • COUNT(*):总记录数 • COUNT(submit_time):非 NULL 的交卷数(即已完成) |
| 7 | ROUND((...) * 1.0 / COUNT(*), 3) |
计算未完成率: • * 1.0 强制转为浮点数,避免整数除法(如 1/3=0) • ROUND(..., 3) 保留三位小数 |
| 8 | FROM exam_record |
数据源 |
| 9 | GROUP BY exam_id |
按试卷分组,进行聚合统计 |
| 12-14 | WHERE incomplete_rate > 0 |
过滤掉"全部完成"的试卷(即未完成率为 0 的) |
易错点与注意事项
| 问题 | 说明 | 如何避免 |
|---|---|---|
❌ WHERE 中使用聚合函数 |
COUNT() 不能在 WHERE 中直接使用 |
使用 HAVING 或子查询 |
| ❌ 整数除法导致结果为 0 | (3-2)/3 = 0(整数除法) |
加 * 1.0 或 CAST(... AS FLOAT) |
❌ 错误使用 COUNT(start_time) |
本题中 start_time 总是非空,可用 COUNT(*) 更简洁 |
统一使用 COUNT(*) 作为总次数 |
❌ 忘记 GROUP BY |
聚合查询必须分组 | 记住:有聚合函数 + 非聚合字段 → 必须 GROUP BY |
❌ 未过滤 incomplete_rate = 0 |
题目要求"有未完成状态" | 外层加 WHERE incomplete_rate > 0 |
💡 关键知识点总结
| 知识点 | 说明 |
|---|---|
COUNT(column) |
只统计 column IS NOT NULL 的行 |
COUNT(*) |
统计所有行,包括 NULL 值 |
聚合函数不能在 WHERE |
必须用 HAVING 或子查询 |
| 浮点除法陷阱 | 整数除法会截断小数,需转浮点 |
ROUND(x, 3) |
保留 3 位小数 |
| 子查询封装 | 先聚合,再过滤,结构更清晰 |
exam_id incomplete_cnt incomplete_rate
9001 1 0.333
复习口诀(助记)
"先分组,再聚合;
总数减完成,得未完数;
乘1.0防整除,ROUND保留三;
外层过滤零,子查结构佳。"