CMD Guide
HomeDatabasesSQL Practice Problems

Students and Examinations

You build the complete student×subject grid with a CROSS JOIN (every student paired with every subject, whether or not they sat the exam), LEFT JOIN the actual exam sittings onto that grid so absent pairs survive as rows with NULL columns, then count a column from the Examinations side — because COUNT(column) skips NULLs, a no-show collapses to exactly 0 while a student who sat the same exam three times counts as 3.

The problem

Three tables. Students(student_id, student_name) and Subjects(subject_name) are the dimensions; Examinations(student_id, subject_name) is a fact table with no primary key — one row per sitting, so the same (student, subject) pair can appear many times. Output one row for every student×subject combination with how many times that student sat that exam, including the zeros, ordered by student_id then subject_name.

The trap is the zeros. If you start from Examinations and group, the pairs nobody sat simply do not exist in the data, so they never appear in the output. The grid has to be manufactured first, independent of attendance.

The mechanism in three moves

  1. Manufacture the grid. Students CROSS JOIN Subjects emits the Cartesian product — every student against every subject. This is the universe of rows we must report, and it exists whether or not a single exam was ever sat.
  2. Attach reality, lossless. LEFT JOIN Examinations on both keys. Where a sitting exists, its columns fill in; where none exists, the grid row is kept and the Examinations columns come back NULL. Using an inner join here would silently delete the zero rows — the whole point of the question.
  3. Count a nullable column, not the row. COUNT(e.student_id) tallies only non-NULL values within each group. A no-show group has one row whose e.student_id is NULL → count 0. A pair sat three times has three non-NULL rows → count 3.

The query

SELECT s.student_id,
       s.student_name,
       sub.subject_name,
       COUNT(e.student_id) AS attended_exams
FROM   Students s
       CROSS JOIN Subjects sub
       LEFT JOIN Examinations e
              ON s.student_id   = e.student_id
             AND sub.subject_name = e.subject_name
GROUP  BY s.student_id, s.student_name, sub.subject_name
ORDER  BY s.student_id, sub.subject_name;

Read the FROM clause as: build the grid (CROSS), keep every grid row (LEFT), then fold each grid cell into one output row (GROUP BY) counting the matches. The ON predicate must name both keys; matching on student_id alone would attach a student's Physics sitting to their Math grid cell too.

diagram
diagram

Worked trace on the real data

Students: Alice (1), Bob (2), Alex (6), John (13). Subjects: Math, Physics, Programming. Examinations sittings: Alice sat Math 3 times, Physics 2 times, Programming 1; Bob sat Math 1 and Programming 1 (never Physics); John sat each once; Alex sat nothing at all.

The CROSS JOIN produces 4×3 = 12 grid rows. After the LEFT JOIN and GROUP BY, each cell counts its matching sittings:

student_idnamesubjectmatched rows from eCOUNT(e.student_id)
1AliceMath3 non-NULL3
1AlicePhysics2 non-NULL2
1AliceProgramming1 non-NULL1
2BobMath1 non-NULL1
2BobPhysics1 row, e.student_id NULL0
2BobProgramming1 non-NULL1
6AlexMath1 row, NULL0
6AlexPhysics1 row, NULL0
6AlexProgramming1 row, NULL0
13JohnMath1 non-NULL1
13JohnPhysics1 non-NULL1
13JohnProgramming1 non-NULL1

The ORDER BY student_id, subject_name then sorts Alex (6) ahead of John (13) numerically and alphabetizes subjects within each student. Note Alex's three zeros: he has no rows in Examinations at all, yet he gets a full set of grid cells because the grid was built before attendance was consulted.

Why the naive version is wrong

Swap in COUNT(*) and the zeros break:

-- WRONG for no-shows
COUNT(*) AS attended_exams

COUNT(*) counts rows in the group, not non-NULL values. After a LEFT JOIN, a no-show pair still occupies exactly one row (the grid row, padded with NULLs). So Bob/Physics and every Alex cell would return 1 instead of 0 — reporting an exam attendance that never happened. The fix is to count any column that is NULL precisely when there was no match, i.e. a column from the LEFT-joined (e) side. This NULL-skipping behaviour of COUNT(column) is the single load-bearing fact of the whole solution; it is why COUNT(e.student_id) and COUNT(*) diverge by exactly the no-shows.

Pitfalls

Takeaways


Problem from LeetCode 1280 "Students and Examinations". Mechanism cross-checked against the PostgreSQL documentation on aggregate functions (COUNT ignores NULL inputs) and the SQL standard's treatment of outer joins producing NULL-extended rows. Re-authored and deepened for this guide: the NULL-skipping insight behind COUNT(e.student_id) vs COUNT(*) is now named explicitly with a worked contrast, the Step 3 placeholder subquery was replaced with the real query and traced rows, and the "raws" typo was corrected.

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

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

🎨 Explain it visually

Build the mental picture, not memorization.

I just read a lesson on **Students and Examinations** (Databases) and want to truly understand it. Explain Students and Examinations from first principles using ONE vivid real-world analogy and a visual mental model — draw it as ASCII art or a clear step-by-step diagram — with a concrete example using real numbers. Then ask me one question to check I got the mental picture, and wait for my reply. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🤔 Walk me through it (interactive)

Socratic — adapts to where you're stuck.

Teach me **Students and Examinations** interactively. Ask me ONE guiding question at a time, wait for my answer, and adapt to my confusion — build the idea with me step by step instead of explaining it all at once. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🧪 Quiz me & fix my gaps

Active recall exposes what you missed.

Quiz me on **Students and Examinations** with 5 questions, easy to tricky, ONE at a time. Tell me if each answer is right; at the end, explain clearly what I got wrong and why. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🧠 Make it stick

Intuition + hook + flashcards for long-term memory.

Help me remember **Students and Examinations** for the long term: give the one-sentence intuition, a memorable hook/mnemonic, a tiny worked example, and 3 active-recall flashcards (Q -> A). If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.

📝 My notes