Average Review Comments by Author
A medium SQL interview practice problem on DataDriven. Write the query and run it against a real database, with instant feedback.
- Domain
- SQL
- Difficulty
- medium
- Seniority
- L3
- Asked in
Problem
Engineering leadership is comparing review burden against tenure, treating each author handle as a case-sensitive identifier that must be matched exactly as it is stored. For every author who shows up in both the review log and the commit history, return the average number of comments across their code reviews alongside the date of their first commit.
Some authors get more feedback than others.
Tables
code_reviews
Columns: review_id INTEGER, repo_name TEXT, author TEXT, reviewer TEXT, status TEXT, comments INTEGER, opened_at TEXT, merged TEXT
| review_id | repo_name | author | reviewer | status | comments | opened_at | merged |
|---|---|---|---|---|---|---|---|
| 1053 | backend-api | bob | eve | changes_requested | 3 | 2026-02-02 | 2026-02-05 |
| 1106 | infra | charlie | frank | pending | 6 | 2026-03-03 | 2026-03-06 |
| 1159 | ml-pipeline | dana | grace | Approved | 9 | 2026-04-04 | 2026-04-07 |
| 1212 | mobile-app | eve | hank | PENDING | 12 | 2026-05-05 | NULL |
| 1265 | data-platform | frank | alice | merged | 15 | 2026-06-06 | 2026-06-09 |
| 1318 | frontend | grace | bob | approved | 18 | 2026-07-07 | 2026-07-10 |
| 1371 | backend-api | hank | charlie | changes_requested | 21 | 2026-08-08 | 2026-08-11 |
| 1424 | infra | alice | NULL | pending | 24 | 2026-09-09 | NULL |
| 1477 | ml-pipeline | bob | eve | Approved | 27 | 2026-01-10 | 2026-01-13 |
| 1530 | mobile-app | charlie | frank | PENDING | 30 | 2026-02-11 | 2026-02-14 |
repo_commits
Columns: commit_id INTEGER, repo_name TEXT, author TEXT, message TEXT, added INTEGER, removed INTEGER, commit_at TEXT
| commit_id | repo_name | author | message | added | removed | commit_at |
|---|---|---|---|---|---|---|
| 3061 | backend-api | bob | Add unit tests | 17 | 7 | 2026-02-02 01:09:00 |
| 3122 | infra | charlie | Refactor auth module | 34 | 14 | 2026-03-03 02:18:00 |
| 3183 | ml-pipeline | charlie | Update deps | 51 | 21 | 2026-04-04 03:27:00 |
| 3244 | mobile-app | dana | Improve perf | 68 | 28 | 2026-05-05 04:36:00 |
| 3305 | data-platform | frank | Add caching layer | 85 | 35 | 2026-06-06 05:45:00 |
| 3366 | frontend | Alice | Fix typo in readme | 102 | 42 | 2026-07-07 06:54:00 |
| 3427 | backend-api | Bob | NULL | 119 | 49 | 2026-08-08 07:03:00 |
| 3488 | infra | alice | WIP | 136 | 56 | 2026-09-09 08:12:00 |
| 3549 | ml-pipeline | bob | 153 | 63 | 2026-01-10 09:21:00 | |
| 3610 | mobile-app | charlie | Add search endpoint | 170 | 70 | 2026-02-11 10:30:00 |
Expected output
| author | avg_comments | earliest_commit |
|---|---|---|
| alice | 19.363636363636363 | 2022-04-17 16:24:00 |
| bob | 23.5 | 2022-01-02 01:09:00 |
| charlie | 22.25 | 2022-02-03 02:54:00 |
| dana | 19.90909090909091 | 2022-03-24 03:39:00 |
| eve | 19.25 | 2022-04-21 04:24:00 |
Worked solution and explanation
What this really is
Strip the business framing and this is two independent per-author rollups stitched together on the author key: one average over review comments, one minimum over commit timestamps. Anyone can write the two aggregates. The whole problem lives in the join key itself, because the commit data carries both 'Alice' and 'alice', and how you treat that casing decides who appears and what their earliest commit date is.
The trap: folding the case
It is tempting to look at 'Alice' and 'alice' and decide they are obviously the same person, then wrap the key in LOWER(author). That single decision quietly rewrites the answer. 'Alice' committed earlier than 'alice', so folding the case pulls that earlier commit into the merged identity and reports a too-early first-commit date for alice, which also bumps alice up the tenure ordering. The reviews side only ever uses lowercase handles, so case-sensitive grouping naturally drops the stray capitalized commit author through the inner join.
- Case-folded (wrong)
- LOWER(author) merges 'Alice' into 'alice'. alice's
earliest_commitjumps to the earlier 'Alice' commit and alice sorts higher than it should. - Exact match (correct)
- GROUP BY author keeps 'Alice' and 'alice' separate. 'Alice' has no matching review row and is dropped by the inner join; alice's
earliest_commitreflects only its own commits.
Building it
Step 1: Average the comments per author
Roll up code_reviews to one row per author with AVG(comments). This average is taken over each author's own review rows only, which is the faithful 'average number of comments on their code reviews'.
Step 2: Find each author's earliest commit
Roll up repo_commits to one row per author with MIN(commit_at). That minimum timestamp is the author's first commit, the tenure signal the ordering depends on.
Step 3: Combine the two rollups on the author key
Inner join the two per-author rollups on author. The inner join restricts the result to authors present in both sources, which excludes pure reviewers with no commits (grace, hank) and the capitalized commit-only handle 'Alice'.
Step 4: Order most tenured first
Order by earliest_commit ascending so the longest-tenured author leads, with author as a deterministic tie-break for shared timestamps.
WITH review_stats AS (
SELECT author, AVG(comments) AS avg_comments
FROM code_reviews
GROUP BY author
),
commit_stats AS (
SELECT author, MIN(commit_at) AS earliest_commit
FROM repo_commits
GROUP BY author
)
SELECT r.author,
r.avg_comments,
c.earliest_commit
FROM review_stats r
JOIN commit_stats c ON r.author = c.author
;Common follow-up questions
- Suppose product confirms 'Alice' and 'alice' really are the same engineer who changed their handle. How would you change the query, and what breaks if you normalize naively? (Tests whether the candidate can defend the deliberate case-sensitive grouping versus a real identity-resolution requirement.)
- What happens to `avg_comments` if you join `code_reviews` to `repo_commits` first and aggregate afterward, instead of aggregating each side independently? (Tests understanding of fan-out: joining the raw tables before aggregating inflates the average.)
- Both tables are large and you group by author every run. What indexing or pre-aggregation would keep these rollups off full table scans? (Tests index design for grouped aggregation over high-row tables.)
- How would you also surface authors who reviewed but never committed, showing a null earliest commit instead of dropping them? (Tests whether the candidate can switch the membership rule to an outer join.)