直接用IN或EXISTS无法表达“选过所有课程”的逻辑,因其仅判断集合包含关系;正确解法是分组统计每个学生选课数并与有效课程总数比对,需用COUNT(DISTINCT course_id)并同步过滤课程状态。
IN 或 EXISTS 子查询不行?很多人一上来就写 SELECT student_id FROM enrollments WHERE course_id IN (SELECT course_id FROM courses),结果得到的是“选过任意课程”的学生,而不是“选过所有课程”的人。核心在于:子查询必须表达“对每门课都存在对应选课记录”,不是简单集合包含关系。
COUNT(DISTINCT ...) + 分组比对是最稳的解法前提是:学生-课程关系存于 enrollments 表(字段为 student_id, course_id),课程总数可查得。关键思路是——算出每个学生选了多少门课,再和总课程数比对是否相等。
SELECT COUNT(*) FROM courses
SELECT student_id, COUNT(DISTINCT course_id) AS cnt FROM enrollments GROUP BY student_id
HAVING 筛出等于总课程数的:HAVING cnt = (SELECT COUNT(*) FROM courses)
注意必须用 COUNT(DISTINCT course_id),避免同一学生重复选同一门课导致计数虚高。
把成绩筛选和“选全课程”拆开做,否则容易漏数据或逻辑错乱。典型错误是写成 WHERE gpa >= 90 AND student_id IN (SELECT ...),但子查询里没关联成绩表,student_id 可能根本不在成绩表中。
student_id 列表,再和 students 或 grades 表 JOIN
SELECT s.name FROM students s JOIN (SELECT student_id FROM enrollments GROUP BY student_id HAVING COUNT(DISTINCT course_id) = (SELECT COUNT(*) FROM courses)) t ON s.id = t.student_id WHERE s.gpa >= 90
gpa 存在另一张表(如 grades),记得 JOIN 时处理可能的 NULL ——用 INNER JOIN 自然过滤掉无成绩者,比 LEFT JOIN + WHERE gpa IS NOT NULL 更安全is_active = 1)时,子查询里的 COUNT 必须同步过滤否则会拿“全部课程数”去比,但学生实际只需选完当前有效的课。常见坑是子查询里忘了加 WHERE is_active = 1,导致本该入围的学生被筛掉。
(SELECT COUNT(*) FROM courses WHERE is_active = 1)
COUNT(DISTINCT CASE WHEN c.is_active = 1 THEN e.course_id END),或者更干脆——先 JOIN courses 再 WHERE c.is_active = 1
WHERE 过滤课程状态,因为分组统计必须基于同一有效课程集多一层状态判断,就多一个对齐点;漏掉任何一个,结果就偏了。