题目描述

✅ 1280. 学生们参加各科测试的次数

image-20260929000842588

image-20260929000842589

image-20261003004605009

题意分析

每个学生都对应每一门科目,结果必须输出所有“学生、科目”组合,并给出参加测试的次数;从未参加某科测试时也要输出,次数为 0。

Students 的学生编号唯一,Subjects 的科目名称唯一,而 Examinations 没有主键:同一学生、同一科目的重复记录表示参加了多次测试,必须逐条计数。最后按学生编号、科目名称排序。

解法:笛卡尔积 + 左连接聚合

核心思路

[!blue]

直接从考试表分组,只能得到至少参加过一次测试的组合。先将学生表与科目表做 CROSS JOIN,让每个学生与每门科目各配对一次,才能得到结果应当展示的完整范围。

再以这个组合表为左表,按“学生编号相同且科目名称相同”左连接考试表。某个组合有几条考试记录,就匹配出几行;没有考试记录时,左连接也会保留这个组合,但右表各列为 NULL。

按学生和科目分组后,使用 COUNT(e.subject_name) 统计右表的非空值。实际匹配的每条考试记录贡献 1,未匹配时补出的空行贡献 0,因此不需要额外补零。不能使用 COUNT(*),也不能统计左表的科目列,因为它们都会把未匹配的保留行算成一次。

解题步骤

  1. 用 Students CROSS JOIN Subjects 生成全部学生与科目的组合。
  2. LEFT JOIN Examinations,在 ON 中同时匹配学生编号和科目名称。
  3. 按学生编号、学生姓名和科目名称分组,让每个组合只输出一行。
  4. 用 COUNT(e.subject_name) 计算测试次数,并命名为 attended_exams。
  5. 按 s.student_id、sub.subject_name 升序排列结果。

代码实现

SELECT
    s.student_id,
    s.student_name,
    sub.subject_name,
    -- 无匹配时此列为空,统计非空列得到零而不是一
    COUNT(e.subject_name) AS attended_exams
FROM Students AS s
-- 先生成所有学生与科目的组合,包括从未考试者
CROSS JOIN Subjects AS sub
-- 同时按学生和科目匹配,保留没有对应考试的组合
LEFT JOIN Examinations AS e
    ON e.student_id = s.student_id
   AND e.subject_name = sub.subject_name
GROUP BY s.student_id, s.student_name, sub.subject_name
ORDER BY s.student_id, sub.subject_name;

复杂度分析

  • 时间复杂度:设学生数 S、科目数 C、流水数 E。哈希连接与聚合的常见估算为 $O(SC+E)$,结果排序另为 $O(SC\log(SC+1))$,实际取决于执行计划。
  • 空间复杂度:中间连接与分组的常见上界为 $O(SC+E)$,由数据库所选算法决定。

关键点总结

[!green]

  • CROSS JOIN 决定应当显示哪些组合,LEFT JOIN 为每个组合补充考试记录。
  • 分组后统计右表的非空列,能同时正确处理多次考试与零次考试。
  • 考试表中的重复行是有效次数,不能去重。

易错点总结

[!yellow]

  • 从考试表直接分组或改成内连接,都会漏掉零次考试的组合。
  • COUNT(*) 和 COUNT(sub.subject_name) 都会把左连接补出的行计为 1;需要统计 e 中用于匹配的列。
  • 不要使用 COUNT(DISTINCT e.subject_name),同一科目参加多次测试也必须计为多次。
  • 连接条件缺少学生编号或科目名称,都会把其他组合的考试记录混入当前计数。
  • 在 WHERE 中要求右表字段非空,会把左连接保留下来的零次组合再次过滤掉。

相似题目

题目 难度 关联与区别
175. 组合两个表 简单 先保留每个学生与科目的全部组合,再左连接实际考试记录,缺考组合也必须出现。
182. 查找重复的电子邮箱 简单 同样分组计数,但本题外连接补出的空行不能算一次考试,应计右表实际匹配记录。
转载与许可
作者
链接 https://hgnulb.github.io/blog/2024/57883384
许可 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!