Off Target

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
Asked in
American Express

Problem

ml_models holds one row per trained model version, stamped with the moment training finished and the accuracy it scored against a 0.95 target. Every scored run finished from the start of 2026 through the April 4 freeze, that day included, belongs in one of ten tenths by how far it lands from the target, the farthest in tenth 10. When two runs sit the same distance off, the lower model id takes the nearer tenth.

Most models are fine. The bottom 10% are not.

Tables

ml_models

Columns: model_id INTEGER, mdl_name TEXT, version TEXT, accuracy REAL, status TEXT, train_at TEXT, framework TEXT

ml_models: 10 of 200 rows
model_idmdl_nameversionaccuracystatustrain_atframework
123rec_enginev1.10.72training2026-02-02 01:00:00TensorFlow
146churn_predv2.00.89archived2026-03-03 02:00:00XGBoost
169spam_filter3.00.61failed2026-04-04 03:00:00sklearn
192img_classifyv2.1-beta0.78validating2026-05-05 04:00:00pytorch
215sentiment1.0.00.95Deployed2026-06-06 05:00:00tensorflow
238demand_fcstv1.00.67deployedNULLPyTorch
261price_optv1.10.84Training2026-08-08 07:00:00TensorFlow
284fraud_detectv2.0NULLarchived2026-09-09 08:00:00XGBoost
307rec_engine3.00.73failed2026-01-10 09:00:00sklearn
330churn_predv2.1-beta0.9validating2026-02-11 10:00:00pytorch

Expected output

First 5 of 36 rows
model_idmdl_nametrain_dateaccuracyaccuracy_gaptenth
767sentiment2026-03-020.980.0300000000000000271
1802churn_pred2026-03-190.980.0300000000000000271
1549price_opt2026-01-080.910.0399999999999999251
951sentiment2026-02-100.990.0400000000000000361
1986churn_pred2026-02-270.990.0400000000000000362

Worked solution and explanation

What this really tests

Take away the ML framing and this is decile banding over an absolute deviation, run on a column that misrepresents its own type. Anyone can type NTILE(10). What separates answers is which rows go into it. Band on raw accuracy and a 0.99 model looks like one of the best in the fleet, when it is 0.04 off. Leave the unscored models in and their NULL gaps sort to the front, filling tenth 1 and pushing every real model outward. And train_at is a full timestamp stored as text. Compare it raw against '2026-04-04' and every freeze-day run disappears. Because every tenth is sized from the whole population, the boundaries around that gap move too.


Working through it

Step 1: Convert the timestamp to a day once

In the inner dated query, CAST(train_at AS DATE) AS train_date turns '2026-04-04 03:00:00' into '2026-04-04'. Cast once, and the day you filter on is the day you report. Compute ABS(accuracy - 0.95) AS accuracy_gap in the same pass, so a model at 0.99 and one at 0.91 both sit 0.04 away.

Step 2: Fix the population before you band it

train_date BETWEEN '2026-01-01' AND '2026-04-04' compares dates to dates, so a run finished at any hour on freeze day is kept. A NULL train_at casts to a NULL train_date and fails the test. accuracy BETWEEN 0 AND 1 keeps only scored runs, because a NULL fails every comparison. Both filters have to act before the banding, since every tenth is measured against whatever survives.

Step 3: Band the survivors

The window function runs after the WHERE, so NTILE(10) OVER (ORDER BY accuracy_gap, model_id) splits only the in-window, scored runs. Ascending gap puts the nearest runs in tenth 1 and the farthest in tenth 10. Accuracy has two decimals, so equal gaps are common, and model_id decides which tenth a tied run lands in instead of engine row order.


The solution

Every run, banded by distance from target
SELECT
  model_id,
  mdl_name,
  train_date,
  accuracy,
  accuracy_gap,
  NTILE(10) OVER (ORDER BY accuracy_gap, model_id) AS tenth
FROM (
  SELECT
    model_id,
    mdl_name,
    CAST(train_at AS DATE) AS train_date,
    accuracy,
    ABS(accuracy - 0.95) AS accuracy_gap
  FROM ml_models
) AS dated
WHERE train_date BETWEEN '2026-01-01' AND '2026-04-04'
  AND accuracy BETWEEN 0 AND 1

One CAST(train_at AS DATE) drives both the window test and the reported train_date, and NTILE(10) sizes the tenths from the rows that survive the WHERE.

Timestamp left as text
train_at BETWEEN '2026-01-01' AND '2026-04-04' compares '2026-04-04 03:00:00' to '2026-04-04' character by character. The longer string sorts after, so the row fails. Freeze-day runs are missing from the output, and every run behind them in the gap order moves toward a different tenth.
Timestamp cast to a date
CAST(train_at AS DATE) produces train_date, so every comparison happens at date grain. A freeze-day run finished at any hour counts, the population is whole, and each tenth is sized from the right count.

Common follow-up questions

Common follow-up questions