Best Accuracy to Training Time Ratio
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
- L4
Problem
A model's training time is its training timestamp measured in days since the Unix epoch, fractions of a day included, and a model missing either that timestamp or an accuracy score cannot be measured at all. From ml_models, find the three models delivering the least accuracy per million days of training time, lowest first, showing each one's name, accuracy, training timestamp, training days and that rate.
Fast to train. Accurate too. Which model?
Tables
ml_models
Columns: model_id INTEGER, mdl_name TEXT, version TEXT, accuracy REAL, status TEXT, train_at TEXT, framework TEXT
| model_id | mdl_name | version | accuracy | status | train_at | framework |
|---|---|---|---|---|---|---|
| 123 | rec_engine | v1.1 | 0.72 | training | 2026-02-02 01:00:00 | TensorFlow |
| 146 | churn_pred | v2.0 | 0.89 | archived | 2026-03-03 02:00:00 | XGBoost |
| 169 | spam_filter | 3.0 | 0.61 | failed | 2026-04-04 03:00:00 | sklearn |
| 192 | img_classify | v2.1-beta | 0.78 | validating | 2026-05-05 04:00:00 | pytorch |
| 215 | sentiment | 1.0.0 | 0.95 | Deployed | 2026-06-06 05:00:00 | tensorflow |
| 238 | demand_fcst | v1.0 | 0.67 | deployed | NULL | PyTorch |
| 261 | price_opt | v1.1 | 0.84 | Training | 2026-08-08 07:00:00 | TensorFlow |
| 284 | fraud_detect | v2.0 | NULL | archived | 2026-09-09 08:00:00 | XGBoost |
| 307 | rec_engine | 3.0 | 0.73 | failed | 2026-01-10 09:00:00 | sklearn |
| 330 | churn_pred | v2.1-beta | 0.9 | validating | 2026-02-11 10:00:00 | pytorch |
Expected output
| mdl_name | accuracy | train_at | training_days | accuracy_per_million_days |
|---|---|---|---|---|
| sentiment | 0.55 | 2026-09-06 21:00:00 | 20702.875 | 26.5664 |
| sentiment | 0.55 | 2026-01-18 21:00:00 | 20471.875 | 26.8661 |
| churn_pred | 0.56 | 2026-09-03 02:00:00 | 20699.083333333332 | 27.0543 |
Worked solution and explanation
What this really is
This is a type-coercion problem wearing a model-quality costume. The sort is trivial; the denominator is not. train_at is text, and STRFTIME('%s', train_at) hands back text too, so dividing it by 86400 quietly becomes integer division and every training time snaps to a whole day. The second trap is quieter still: the list runs lowest first, and a NULL rate sorts ahead of every real one. Skip the null filter and your bottom three are models nobody ever scored.
Step 1: Drop the rows that cannot be measured
Filter accuracy IS NOT NULL AND train_at IS NOT NULL. Either NULL makes the rate NULL, and an ascending sort hands those rows the first slots. Both conditions matter on this data: the seed has rows missing each one.
Step 2: Turn train_at into fractional days
STRFTIME('%s', train_at) gives epoch seconds as text. Wrap it in CAST(... AS FLOAT) before dividing by 86400, so a model trained at 09:00 carries its 0.375 of a day instead of losing it.
Step 3: Use the same denominator in the rate
A SELECT cannot reference its own training_days alias, so the rate repeats the cast expression: 1000000 * accuracy / (CAST(...) / 86400). Keep the cast in both places; drop it from either and that column silently goes back to whole days. Scaling by a million keeps the values readable at four decimals.
Step 4: Lowest first, keep three
Sort on accuracy_per_million_days ascending and cut with LIMIT 3. The rates are distinct on the seed, so no tiebreaker is owed at the cut.
SELECT
mdl_name,
accuracy,
train_at,
CAST(STRFTIME('%s', train_at) AS FLOAT) / 86400 AS training_days,
ROUND(1000000 * accuracy / (CAST(STRFTIME('%s', train_at) AS FLOAT) / 86400), 4) AS accuracy_per_million_days
FROM ml_models
WHERE
accuracy IS NOT NULL
AND train_at IS NOT NULL
ORDER BY
accuracy_per_million_days ASC
LIMIT 3- Raw division
STRFTIME('%s', train_at) / 86400coerces the text to an integer and divides integer by integer. A model trained at '2026-01-10 09:00:00' lands on day 20463 flat, and every rate shifts.- Cast first
CAST(STRFTIME('%s', train_at) AS FLOAT) / 86400keeps the fraction: day 20463.375. The query runs either way, which is exactly why the wrong one survives review.
Common follow-up questions
- What changes if a real `training_duration_seconds` column existed? (Whether you see the epoch denominator as a proxy and would swap in the true duration.)
- Return the three weakest models per `framework` instead of overall. (Moving from a global `LIMIT` to a per-group selection.)
- Two models tie exactly at the third slot. Which one should win? (When a cut through tied rows needs a stated tie rule and an extra sort key.)
- Why might `STRFTIME('%s', train_at)` return `NULL` on a non-null `train_at`? (Malformed timestamp strings silently fall out of the list.)