Knowledge Guide
HomeDatabasesSQL Practice Problems

medium Students Who Attended All Courses

This is relational division: “find the students whose set of taken courses contains the full set of offered courses,” and the cheap way to test set-containment is to count — if a student’s number of distinct required courses equals the total number of required courses, their set must cover all of them, because you cannot reach that count without hitting every course at least once.

The problem

Given enrollments and a master list of courses, return the students who enrolled in every course.

Enrollment

student_idcourse_id
100200
200300
300200
300300
100300

Course = { 200, 300 }  →  total required = 2

Expected output: 100, 300.

The counting solution

SELECT student_id
FROM   Enrollment
GROUP  BY student_id
HAVING COUNT(DISTINCT course_id) = (SELECT COUNT(*) FROM Course)
ORDER  BY student_id;

The right-hand side computes the divisor once (the count of all courses). For each student, HAVING keeps only those whose distinct-course count matches it. COUNT(*) FROM Course assumes course_id is the unique key of Course — if it could contain duplicates, use COUNT(DISTINCT course_id) there too so the divisor is the size of the real course set.

Traced, student by student

Divisor = COUNT(*) FROM Course = 2.

student_idcourse_ids seenCOUNT(DISTINCT course_id)= 2 ?kept
100200, 3002yes
2003001no
300200, 3002yes

Student 200 enrolled in only course 300, so its distinct count (1) falls short of the divisor (2) and is filtered out. Students 100 and 300 each cover the whole set.

diagram
diagram

The other canonical form: double-negation with NOT EXISTS

Relational algebra has no native “divide” operator, so the textbook translation expresses division as a double negative: a student qualifies when there is no course that the student did not take.

SELECT s.student_id
FROM   (SELECT DISTINCT student_id FROM Enrollment) s
WHERE  NOT EXISTS (
         SELECT 1
         FROM   Course c
         WHERE  NOT EXISTS (
                  SELECT 1
                  FROM   Enrollment e
                  WHERE  e.student_id = s.student_id
                    AND  e.course_id  = c.course_id
         )
);

Read it inside-out: the inner NOT EXISTS means “student s did not take course c”; the outer NOT EXISTS means “there is no such missing course.” Both queries return the same answer here (100, 300). The counting form is shorter and usually faster on this schema; the NOT EXISTS form is the one that generalizes — it works unchanged when “cover the set” is defined by a join condition rather than equal counts, and it short-circuits on the first missing course instead of aggregating every row.

Why the count must be DISTINCT (the naive bug)

It is tempting to write HAVING COUNT(course_id) = (SELECT COUNT(*) FROM Course). That breaks the moment Enrollment can hold duplicate (student, course) pairs — e.g. a student who re-enrolls in course 300 twice and never touches course 200 would have COUNT(course_id) = 2 and be wrongly reported as having taken all courses. COUNT(DISTINCT course_id) counts the set of courses, not the number of enrollment rows, which is the quantity relational division actually requires.

Pitfalls

Takeaways


Re-authored and deepened for this guide. Problem adapted from the LeetCode-style “Students Who Attended All Courses” exercise. The relational-division framing, the count-equals-cardinality theorem, and the double-negation translation follow Garcia-Molina, Ullman & Widom, Database Systems: The Complete Book (2nd ed.), and Date, An Introduction to Database Systems; the count-vs-NOT EXISTS performance trade-offs are standard SQL practitioner lore (Joe Celko, SQL for Smarties).

🤖 Don't fully get this? Learn it with Claude

Stuck on Students Who Attended All Courses? Open Claude, copy a block below, and it'll teach you this exact concept — visually and interactively.

🪜 Hint ladder (no spoilers)

Progressively stronger hints — you still solve it.

I'm working on the problem **Students Who Attended All Courses** (Databases). Give me a HINT LADDER: start with the tiniest nudge, then wait. Only reveal the next, stronger hint when I ask. Do NOT show the full solution unless I type 'show solution'. Keep me doing the thinking. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🎨 Explain the approach visually

See the technique, not just code.

Explain the optimal approach to **Students Who Attended All Courses** with a VISUAL walkthrough: trace it on a small concrete example using ASCII art / a step-by-step diagram, narrate what changes each step, then give time & space complexity with a one-line derivation. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🔍 Review my solution

Catch bugs, edge cases, sub-optimality.

I'll paste my solution to **Students Who Attended All Courses**. Review it for correctness, missed edge cases, and time/space complexity, then coach me toward the optimal — don't just rewrite it. Ask me to paste my code now. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🔁 Drill the pattern

Lock in recognition with look-alikes.

Give me 2 problems that use the SAME underlying pattern as **Students Who Attended All Courses**. For each, let me attempt first, then review my answer and name the trigger signal that reveals the pattern. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.

📝 My notes