Driver earnings rank
Ops wants a leaderboard of drivers by money earned.
A driver’s earnings are the sum of fare over their trips with status = 'completed'. Leave out
drivers with no completed trips.
Rank drivers by earnings, highest first, starting at 1. Drivers with equal earnings share a rank, and the next rank skips ahead: two drivers tied at 1 are followed by rank 3. (This is “competition” ranking, like a race with a shared podium place.)
Return one row per driver:
| column | type | meaning |
|---|---|---|
driver_id |
integer | the driver |
name |
text | the driver’s name |
earnings |
decimal | total completed fare, rounded to 2 decimals |
earnings_rank |
integer | 1 for the top earner; ties share a rank |
Row order doesn’t matter.
Tables: drivers, trips.
ridelog
A ride-hailing app across five cities. Synthetic data, generated by scripts/gen-datasets.mjs.
Click a table to preview its first 10 rows.
drivers 50 rows
50 drivers.
trips 2,000 rows
2,000 trips over 60 days from 2025-03-01. Cancelled trips have NULL ended_at, distance_km and fare.
events 3,146 rows
app events for 120 users. About 5% were ingested twice: same user_id, event_type, event_ts, different event_id and later ingested_at.
driver_days 578 rows
one row per day a driver was online (first 20 drivers, 60 days). Gaps are days off.
Hints get more specific as you go. Reveal one, try again, then come back.
Get one row per driver first: join trips to drivers, keep completed trips, GROUP BY the driver.
A window function can rank the grouped rows in the same SELECT. Window functions run after
GROUP BY, so you can write ... OVER (ORDER BY SUM(t.fare) DESC).
There are three ranking functions. ROW_NUMBER() never ties. DENSE_RANK() ties without gaps
(1, 1, 2). RANK() ties with gaps (1, 1, 3). This problem wants RANK().
Try one more hint first? The solution is always here, but working it out is what makes it stick.
Approach
Group to one row per driver, then rank the groups by their sum in the same SELECT.
SELECT d.driver_id,
d.name,
ROUND(SUM(t.fare), 2) AS earnings,
RANK() OVER (ORDER BY SUM(t.fare) DESC) AS earnings_rank
FROM drivers d
JOIN trips t ON t.driver_id = d.driver_id
WHERE t.status = 'completed'
GROUP BY d.driver_id, d.name;
Why it works
SQL evaluates WHERE, then GROUP BY, then window functions, then the final SELECT list. So the
window sees one row per driver and can order by an aggregate. You don’t need a subquery.
RANK() gives each row 1 + the number of rows that sort strictly ahead of it. Two drivers tied for
first both have zero rows ahead, so both get 1, and the next driver has two ahead, so gets 3.
DENSE_RANK() would give 2, and ROW_NUMBER() would split the tie arbitrarily.
The inner join plus WHERE status = 'completed' drops drivers with no completed trips, as asked.
Under the hood
The hash aggregate produces 50 rows. The window operator then sorts them by SUM(t.fare) DESC and
walks the sorted run once. For RANK it only remembers the previous sort key and the current row
number: same key, reuse the rank; new key, rank = row number. No partition means one sort over the
whole result.
Per-problem discussion threads are coming. Until then, questions and better solutions are welcome on LinkedIn or by email at hello@builtfrombits.com.
Press ⌘ ↵ or Run to check the visible tests. Submit runs the hidden ones too. Everything runs in your browser.