SQL185 试卷完成数同比2020年的增长率及排名变化
描述
现有试卷信息表examination_info(exam_id试卷ID, tag试卷类别, difficulty试卷难度, duration考试时长, release_time发布时间):
试卷作答记录表exam_record(uid用户ID, exam_id试卷ID, start_time开始作答时间, submit_time交卷时间, score得分):
请计算2021年上半年各类试卷的做完次数相比2020年上半年同期的增长率(百分比格式,保留1位小数),以及做完次数排名变化,按增长率和21年排名降序输出。
由示例数据结果输出如下:
解释:2020年上半年有3个tag有作答完成的记录,分别是C++、SQL、PYTHON,它们被做完的次数分别是3、3、2,做完次数排名为1、1(并列)、3;
2021年上半年有2个tag有作答完成的记录,分别是算法、SQL,它们被做完的次数分别是3、2,做完次数排名为1、2;具体如下:
因此能输出同比结果的tag只有SQL,从2020到2021年,做完次数3=>2,减少33.3%(保留1位小数);排名1=>2,后退1名。
WITHt2 AS (SELECTexam_id,IF(start_year = '2020', exam_cnt, NULL) exam_cnt_20, -- 2020年的完成次数LEAD(exam_cnt, 1) OVER (PARTITION BYexam_idORDER BYstart_year) exam_cnt_21, -- 2021年的完成次数IF(start_year = '2020', rk, NULL) exam_cnt_rank_20, -- 2020年的排名LEAD(rk, 1) OVER (PARTITION BYexam_idORDER BYstart_year) exam_cnt_rank_21 -- 2021年的排名FROM(SELECTexam_id,YEAR(submit_time) start_year,COUNT(score) exam_cnt,RANK() OVER (PARTITION BYYEAR(submit_time)ORDER BYCOUNT(score) DESC) rk/*分别对2021和2020的做完情况进行排名*/FROMexam_recordWHEREMONTH(submit_time) BETWEEN 1 AND 6 -- 选取上半年数据AND submit_time BETWEEN '2020-01-00 00:00:00' AND '2022-01-01 00:00:00' -- 选取2020和2021年的数据GROUP BYYEAR(submit_time),exam_id/*对年份和类别进行聚类*/) t1)
SELECTa.tag,exam_cnt_20,exam_cnt_21,CONCAT(ROUND((exam_cnt_21 - exam_cnt_20) * 100 / exam_cnt_20,1),'%') AS growth_rate,exam_cnt_rank_20,exam_cnt_rank_21,CAST(exam_cnt_rank_21 AS SIGNED) - CAST(exam_cnt_rank_20 AS SIGNED) AS rank_delta -- 需要转换格式,否则会报错
FROMt2LEFT JOIN examination_info AS a ON a.exam_id = t2.exam_id
WHEREexam_cnt_21 IS NOT NULL
ORDER BYgrowth_rate DESC,exam_cnt_rank_21 DESC;
🔍 代码逐层解析
🧱 1. 内层查询 t1
—— 按年份+试卷分组并排名
SELECTexam_id,YEAR(submit_time) AS start_year,COUNT(score) AS exam_cnt,RANK() OVER (PARTITION BY YEAR(submit_time)ORDER BY COUNT(score) DESC) AS rk
FROM exam_record
WHEREMONTH(submit_time) BETWEEN 1 AND 6AND submit_time >= '2020-01-01' AND submit_time < '2022-01-01'
GROUP BY YEAR(submit_time), exam_id
✅ 做了什么?
- 筛选 2020 和 2021 年上半年 的数据
- 按 年份 + 试卷 ID 分组
- 统计每类试卷每年的完成次数(
COUNT(score)
) - 使用
RANK()
计算每年内的完成次数排名(降序)
⚠️ 注意:
BETWEEN 1 AND 6
:精确筛选上半年submit_time < '2022-01-01'
:避免包含 2022 年数据RANK()
处理并列情况(如 3,3 → 排名 1,1,下一名为 3)
🧱 2. 中层查询 t2
—— 使用 LEAD()
对齐两年数据
SELECTexam_id,IF(start_year = '2020', exam_cnt, NULL) AS exam_cnt_20,LEAD(exam_cnt, 1) OVER (PARTITION BY exam_id ORDER BY start_year) AS exam_cnt_21,IF(start_year = '2020', rk, NULL) AS exam_cnt_rank_20,LEAD(rk, 1) OVER (PARTITION BY exam_id ORDER BY start_year) AS exam_cnt_rank_21
FROM t1
✅ 核心技巧:LEAD()
窗口函数
函数 | 作用 |
---|---|
LEAD(col, 1) | 获取当前行之后第 1 行的值 |
PARTITION BY exam_id | 按试卷分组,确保只在同 exam_id 内查找 |
ORDER BY start_year | 按年份升序排列(2020 → 2021) |
💡 举个例子:
原始 t1
数据:
exam_id | start_year | exam_cnt | rk |
---|---|---|---|
9001 | 2020 | 100 | 2 |
9001 | 2021 | 150 | 1 |
经过 LEAD()
后:
exam_id | exam_cnt_20 | exam_cnt_21 | rk_20 | rk_21 |
---|---|---|---|---|
9001 | 100 | 150 | 2 | 1 |
✅ 实现了“将两年数据对齐到同一行”
📌 IF(start_year = '2020', ..., NULL)
的作用:
- 将 2020 年的数据保留在
exam_cnt_20
字段 - 2021 年该字段为
NULL
- 配合
LEAD()
,确保exam_cnt_21
是下一年的值
🧱 3. 主查询 —— 计算增长率与排名变化
SELECTa.tag,exam_cnt_20,exam_cnt_21,CONCAT(ROUND((exam_cnt_21 - exam_cnt_20) * 100.0 / exam_cnt_20, 1), '%') AS growth_rate,exam_cnt_rank_20,exam_cnt_rank_21,CAST(exam_cnt_rank_21 AS SIGNED) - CAST(exam_cnt_rank_20 AS SIGNED) AS rank_delta
FROM t2
LEFT JOIN examination_info AS a ON a.exam_id = t2.exam_id
WHERE exam_cnt_21 IS NOT NULL
ORDER BY growth_rate DESC, exam_cnt_rank_21 DESC;
✅ 关键计算:
指标 | 公式 | 说明 |
---|---|---|
增长率 | (2021 - 2020) / 2020 * 100% | * 100.0 保证浮点运算 |
格式化输出 | CONCAT(..., '%') | 添加百分号 |
排名变化 | rank_21 - rank_20 | 正数表示排名下降,负数表示上升 |
类型转换 | CAST(... AS SIGNED) | 避免字符串减法报错 |
✅ 过滤与排序:
WHERE exam_cnt_21 IS NOT NULL
:确保该试卷在 2020 和 2021 都存在ORDER BY growth_rate DESC
:增长率从高到低exam_cnt_rank_21 DESC
:2021 年排名靠后的优先(同增长率时)
📝 核心知识点总结
技术点 | 说明 | 应用场景 |
---|---|---|
LEAD()/LAG() | 获取下一行/上一行的值 | 同比、环比分析 |
RANK() | 处理并列排名 | 排行榜、绩效排名 |
PARTITION BY | 窗口函数分组 | 分组内排序、对比 |
CONCAT + ROUND | 格式化数值输出 | 百分比、金额显示 |
CAST(... AS SIGNED) | 类型转换 | 字符串转整数计算 |
WITH ... AS () | CTE 公共表表达式 | 分步处理复杂逻辑 |
✅ 最佳实践建议
时间范围写法:
- ❌
BETWEEN '2020-01-00'
(非法日期) - ✅
submit_time >= '2020-01-01' AND submit_time < '2022-01-01'
- ❌
增长率计算注意除零:
- 可加
WHERE exam_cnt_20 > 0
- 可加
排名函数选择:
RANK()
:允许并列,下一名跳过(1,1,3)DENSE_RANK()
:允许并列,下一名不跳过(1,1,2)ROW_NUMBER()
:强制唯一,无并列
LEAD()
的适用场景:- 跨行对比(如:今年 vs 去年)
- 避免自连接,提升性能
🎯 一句话总结
“用
LEAD()
实现跨年数据对齐,RANK()
计算年度排名,CONCAT+ROUND
格式化增长率,完成试卷类别的同比分析”
这套模式适用于:
- 年度/季度/月度对比分析
- 排名变化监控
- 增长率计算与展示