Top Repos by Successful Builds
An easy SQL interview practice problem on DataDriven. Write the query and run it against a real database, with instant feedback.
- Domain
- SQL
- Difficulty
- easy
- Seniority
- L3
- Asked in
- ESPN
Problem
Only builds already marked success count; queued and running ones have not finished, and failed or canceled ones never will. A release engineer wants every repo in ci_builds with its number of successful builds, sorted from most successful to fewest.
Green builds. Which repos lead?
Tables
ci_builds
Columns: build_id INTEGER, repo_name TEXT, branch TEXT, status TEXT, dur_secs INTEGER, trigger TEXT, built_at TEXT
| build_id | repo_name | branch | status | dur_secs | trigger | built_at |
|---|---|---|---|---|---|---|
| 5047 | backend-api | develop | failed | 34 | pull_request | 2026-02-02 01:13:00 |
| 5094 | infra | feature/auth | canceled | 53 | schedule | 2026-03-03 02:26:00 |
| 5141 | ml-pipeline | fix/login-bug | running | 72 | manual | 2026-04-04 03:39:00 |
| 5188 | mobile-app | release/v2.0 | queued | 91 | tag | 2026-05-05 04:52:00 |
| 5235 | data-platform | hotfix/payment | success | 110 | webhook | 2026-06-06 05:05:00 |
| 5282 | frontend | feature /spaces | failed | NULL | push | 2026-07-07 06:18:00 |
| 5329 | backend-api | main | canceled | 148 | pull_request | 2026-08-08 07:31:00 |
| 5376 | infra | develop | running | 167 | schedule | 2026-09-09 08:44:00 |
| 5423 | ml-pipeline | feature/auth | queued | 186 | manual | 2026-01-10 09:57:00 |
| 5470 | mobile-app | fix/login-bug | success | 205 | tag | 2026-02-11 10:10:00 |
Expected output
| repo_name | success_count |
|---|---|
| data-platform | 8 |
| mobile-app | 8 |
| backend-api | 6 |
| frontend | 6 |
| infra | 6 |
Worked solution and explanation
What this really is
This is a filtered count per group wearing a CI dashboard costume. Everyone writes the GROUP BY repo_name; what separates candidates is deciding which rows are allowed into the count before it happens. ci_builds mixes five statuses and only 'success' is a win: 'queued' and 'running' have not finished, 'failed' and 'canceled' never will. Skip the filter and the answer inverts: every repo lands at 32 or 34 builds, and data-platform, which actually leads with 8 successes, falls to fifth.
Building it
Step 1: Keep only builds marked 'success'
Put status = 'success' in WHERE so rows are filtered before anything is counted: 40 of the 200 rows survive. It cannot live in HAVING, because HAVING sees groups and a group of builds has no single status.
Step 2: Count the survivors per repo_name
GROUP BY repo_name with COUNT(*) counts rows. Count rows, not a column: COUNT(dur_secs) skips NULL durations, and every frontend build in the seed has a NULL dur_secs, so frontend would report 0 instead of 6.
Step 3: Sort most successful first
ORDER BY success_count DESC is the order the statement asks for. Six repos share only two counts (two at 8, four at 6), and the preview lists tied repos alphabetically, so repo_name ASC follows as the second key.
SELECT repo_name, COUNT(*) AS success_count
FROM ci_builds
WHERE status = 'success'
GROUP BY repo_name
ORDER BY success_count DESC, repo_name ASCSELECT repo_name, COUNT(*) AS success_count
FROM ci_builds
GROUP BY repo_name
ORDER BY success_count DESC, repo_name ASCCounts every build, so it measures activity, not success.
- Every build
- No filter:
backend-api,infra,ml-pipelineandmobile-appat 34,data-platformandfrontendat 32. The column is namedsuccess_countbut holds total volume. - Successful builds only
WHERE status = 'success'first:data-platformandmobile-appat 8, the other four at 6. The repo that leads on success sits fifth on raw volume.
Common follow-up questions
- If `status` held both 'SUCCESS' and 'success', how would you count both? (Tests case normalization: compare `LOWER(status) = 'success'` instead of the raw value.)
- Should a repo with zero successful builds appear with a 0? (Tests that a `WHERE` filter drops such repos entirely; keeping them needs a conditional count over all rows.)
- How would you add each repo's success rate next to the count? (Tests conditional aggregation: `SUM(CASE WHEN status = 'success' THEN 1 ELSE 0 END) * 1.0 / COUNT(*)` over unfiltered rows, avoiding integer division.)