Запрос, чтобы найти студентов, которые не прошли все заданные предметы - PullRequest
1 голос
/ 17 октября 2019

Я пытаюсь найти студентов, которые провалили каждый предмет в наборе предметов с помощью запросов PostgreSQL.

Студенты провалили предмет, если у них не нулевая оценка <50 хотя бы для одного предложения курсапредмета. И я хочу найти студентов, которые не прошли все предметы в наборе предметов <code>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. Как я могу это сделать?

Ответы [ 2 ]

1 голос
/ 17 октября 2019

Учащиеся проваливают предмет, если у них есть ненулевая оценка <50, по крайней мере, для одного предложения предмета по курсу</p>

Один из многих возможных способов:

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. Формирование картеанского произведения студентов и соответствующих предметов.

  2. Агрегировать по учащимся (s.id) и отфильтровать тех, кто провалил все предметов в предложении HAVING с bool_and() в коррелированном EXISTS тестировании подзапроса, по крайней мере, дляодин такой неудачный курс для каждой комбинации студент-предмет.

  3. Присоединитесь к people в качестве последнего косметического шага, чтобы получить имена учеников. Я добавил id, чтобы получить уникальные результаты (поскольку имена , вероятно, не гарантированно будут уникальными).

В зависимости от фактического определения таблицы, ваша версия Postgres,кардинальности и распределения значений, могут быть (намного) более эффективные запросы.

Это случай в его ядре. См .:

И самая эффективная стратегия - исключить как можно больше студентовкак можно раньше в запросе, как, например, сначала проверяя предмет с наименьшим количеством учеников-неудачников. Затем продолжите работу только с оставшимися учащимися и т. Д.

Ваш случай добавляет особую сложность, заключающуюся в том, что количество и идентичность испытуемых предметов неизвестны / динамичны. Как правило, рекурсивный CTE или аналогичный предлагает лучшую производительность для такого рода проблем:

1 голос
/ 17 октября 2019

Я бы использовал агрегацию:

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);
...