查询以查找未通过所有给定科目的学生

Query to find students that failed all given subjects

我试图通过 PostgreSQL 查询找出一组科目中每个科目都不及格的学生。

如果学生的至少一门课程的非零分数 < 50,则学生不及格。我想找到所有科目都不及格的学生 Relevant_subjects.
注意:学生每门课程可以有多个记录。

SELECT People.name
FROM 
    Relevant_subjects
    JOIN Courses on (Courses.subject = Relevant_subjects.id)
    JOIN Course_enrolments on (Course_enrolments.course = Courses.id)
    JOIN Students on (Students.id = Course_enrolments.student)
    JOIN People on (People.id = Students.id)
WHERE
    Course_enrolments.mark is not null AND
    Course_enrolments.mark < 50 AND
;

使用上面的代码,我得到了没有通过任何 Relevant_subjects 的学生,但我想要的结果是得到没有通过所有 Relevant_subjects 的学生。我该怎么做?

我会使用聚合:

SELECT p.name
FROM Relevant_subjects rs JOIN
     Courses c
     ON c.subject = rs.id JOIN
     Course_enrolments ce
     ON ce.course = c.id JOIN
     Students s
     ON s.id = ce.student JOIN
     People p
     ON p.id = s.id
WHERE ce.mark < 50
GROUP BY p.id, p.name
HAVING COUNT(*) = (SELECT COUNT(*) FROM relevant_subjects);

注意:此版本假定学生每门课程只有一个记录并且 relevant_subjects 没有重复项。如有必要,可以使用 COUNT(DISTINCT) 轻松处理这些问题。

要处理重复项,这看起来像:

SELECT p.name
FROM Relevant_subjects rs JOIN
     Courses c
     ON c.subject = rs.id JOIN
     Course_enrolments ce
     ON ce.course = c.id JOIN
     Students s
     ON s.id = ce.student JOIN
     People p
     ON p.id = s.id
WHERE ce.mark < 50
GROUP BY p.id, p.name
HAVING COUNT(DISTINCT rs.id) = (SELECT COUNT(DISTINCT rs2.id) FROM relevant_subjects rs2);

Students fail a subject if they have a not null mark < 50 for at least one course offering of the subject.

许多种可能方式之一:

SELECT id, p.name
FROM  (
   SELECT s.id
   FROM   students                s
   CROSS  JOIN relevant_subjects rs
   GROUP  BY s.id
   HAVING bool_and( EXISTS(
            SELECT -- empty list
            FROM   course_enrolments ce
            JOIN   courses           c  ON c.id = ce.course
            WHERE  ce.mark < 50  -- also implies NOT NULL
            AND    ce.student = s.id
            AND    c.subject = rs.id
            )
         ) -- all failed
   ) sub
JOIN   people p  USING (id);
  1. 形成学生和相关科目的 Carthesian 积。

  2. 按学生 (s.id) 汇总并过滤 所有 科目中 HAVING 子句中 bool_and()通过相关 EXISTS 子查询测试,针对每个学生-学科组合至少一门这样的失败课程。

  3. 加入 people 作为获取学生姓名的最后修饰步骤。我添加了 id 以获得唯一的结果(因为 names 可能不能保证是唯一的)。

根据实际 table 定义、您的 Postgres 版本、基数和值分布,可能会有(很多)更高效的查询。

这是一个 的核心案例。参见:

  • How to filter SQL results in a has-many-through relation

最有效的策略是尽可能早地在查询中排除尽可能多的学生——比如首先检查失败学生最少的科目。然后只处理剩下的学生等等

您的案例增加了待测对象的数量和身份未知/动态的具体困难。通常,递归 CTE 或类似的方法可以为此类问题提供最佳性能: