Mock Test Sittings for Every Learner and Track — SQL Medium Problem
A placement-prep centre runs mock tests in a few tracks (Aptitude, DSA, SQL, …). A learner may sit a track's mock test any number of times, or never.
- Difficulty: Medium
- Topics: Joins, Aggregation
- Dialect: MySQL
- Problem: #15
Problem statement
A placement-prep centre runs mock tests in a few tracks (Aptitude, DSA, SQL, …). A learner may sit a track's mock test any number of times, or never.
Return one row for every pair of a learner and a track — pairs without any sitting included — with the columns learner_id, learner_name, track and sittings (how many times that learner sat that track's test, 0 if never). Order the rows by learner_id, then by track.
Tables
Table: Learner
| Column | Type |
|---|---|
| learner_id | int |
| learner_name | varchar |
Primary key: learner_id.
One row per enrolled learner.
Table: Track
| Column | Type |
|---|---|
| track | varchar |
Primary key: track.
One row per mock-test track.
Table: MockSitting
| Column | Type |
|---|---|
| sitting_id | int |
| learner_id | int |
| track | varchar |
| sat_on | date |
Primary key: sitting_id.
One row per test taken. Every learner_id and track here exists in its own table.
Examples
Example 1
Learner
| learner_id | learner_name |
|---|---|
| 1 | Ananya |
| 2 | Kabir |
| 3 | Saanvi |
Track
| track |
|---|
| Aptitude |
| DSA |
| SQL |
MockSitting
| sitting_id | learner_id | track | sat_on |
|---|---|---|---|
| 1 | 1 | DSA | 2025-03-01 |
| 2 | 1 | Aptitude | 2025-03-02 |
| 3 | 1 | DSA | 2025-03-05 |
| 4 | 3 | SQL | 2025-03-03 |
| 5 | 1 | DSA | 2025-03-08 |
| 6 | 3 | Aptitude | 2025-03-09 |
Output
| learner_id | learner_name | track | sittings |
|---|---|---|---|
| 1 | Ananya | Aptitude | 1 |
| 1 | Ananya | DSA | 3 |
| 1 | Ananya | SQL | 0 |
| 2 | Kabir | Aptitude | 0 |
| 2 | Kabir | DSA | 0 |
| 2 | Kabir | SQL | 0 |
| 3 | Saanvi | Aptitude | 1 |
| 3 | Saanvi | DSA | 0 |
| 3 | Saanvi | SQL | 1 |
How to solve Mock Test Sittings for Every Learner and Track
The answer's rows are not taken from the sittings — they are every combination of a learner and a track, including combinations that never happened. Combinations are what a CROSS JOIN produces: with 3 learners and 3 tracks it yields 9 pairs, which is exactly the grid the answer needs.
Each pair then needs its count. A LEFT JOIN to MockSitting on both learner_id and track attaches every matching sitting — several rows for a pair taken repeatedly, and one row of NULLs for a pair never taken. Grouping by the pair and counting m.sitting_id gives the number of sittings: COUNT of a column skips NULLs, so an untouched pair counts 0, whereas COUNT(*) would count its padded row as 1.
Both join conditions belong in ON. Putting m.track = t.track in WHERE instead would throw away the NULL-padded rows and with them every zero.
Alternatives: a correlated COUNT(*) per pair (no padding involved, so * is right there), or counting the sittings once in a CTE and LEFT JOINing those counts to the grid with COALESCE(n, 0). The grid has learners × tracks rows, so that product bounds the cost; the counting itself is one pass over the sittings.
Reference solution (MySQL)
SELECT l.learner_id, l.learner_name, t.track, COUNT(m.sitting_id) AS sittings
FROM Learner l
CROSS JOIN Track t
LEFT JOIN MockSitting m ON m.learner_id = l.learner_id AND m.track = t.track
GROUP BY l.learner_id, l.learner_name, t.track
ORDER BY l.learner_id, t.trackAnother way
SELECT l.learner_id, l.learner_name, t.track,
(SELECT COUNT(*) FROM MockSitting m WHERE m.learner_id = l.learner_id AND m.track = t.track) AS sittings
FROM Learner l CROSS JOIN Track t
ORDER BY l.learner_id, t.trackAnother way
WITH counts AS (SELECT learner_id, track, COUNT(*) AS n FROM MockSitting GROUP BY learner_id, track)
SELECT l.learner_id, l.learner_name, t.track, COALESCE(c.n, 0) AS sittings
FROM Learner l CROSS JOIN Track t
LEFT JOIN counts c ON c.learner_id = l.learner_id AND c.track = t.track
ORDER BY l.learner_id, t.track← OTP Verification Rate of Every Account · Daily Cancellation Rate Without Suspended Members →