API Call Distribution Fraction

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
Lyft

Problem

Our traffic breakdown shows 'get' and 'GET' as separate methods, splitting what is really one verb across two slices. From api_calls, give each HTTP method and response status pairing its share of all calls as a fraction between 0 and 1, biggest slices first.

Not all endpoints are created equal.

Tables

api_calls

Columns: call_id INTEGER, endpoint TEXT, method TEXT, status INTEGER, latency REAL, user_id INTEGER, call_time TEXT, err_msg TEXT

api_calls: 10 of 670 rows
call_idendpointmethodstatuslatencyuser_idcall_timeerr_msg
8079/api/v1/ordersPOST2004.21002026-02-02 01:07:00NULL
8158/api/v2/productsPUT2007.91972026-03-03 02:14:00NULL
8237/api/v1/searchDELETE20111.62942026-04-04 03:21:00NULL
8316/api/v1/auth/loginPATCH20415.33912026-05-05 04:28:00NULL
8395/api/v1/auth/logoutget30119NULL2026-06-06 05:35:00NULL
8474/api/v2/analyticspost40022.75852026-07-07 06:42:00Not found
8553/api/v1/paymentsGET40126.46822026-08-08 07:49:00Unauthorized
8632/api/v1/users/POST40330.17792026-09-09 08:56:00Rate limit exceeded
8711/api/v2/products/PUT40433.88762026-01-10 09:03:00Internal server error
8790/api/v1/usersDELETE50037.5NULL2026-02-11 10:10:00Bad gateway

Expected output

First 5 of 55 rows
methodstatusfraction
GET2000.7223880597014926
POST2000.022388059701492536
PUT2000.010447761194029851
DELETE2000.008955223880597015
PATCH2000.008955223880597015

Worked solution and explanation

This is a part-of-whole problem presented as a dashboard. Almost everyone writes the count per pairing. What separates candidates is keeping the grand total available after GROUP BY has collapsed the rows: once 670 calls fold into one row per pairing, no row knows the total any more. Two quieter traps sit beside it. If you divide two whole counts, every share truncates to 0. If you group on the raw method, 'get' gets its own slice next to 'GET', and each holds part of what is really one verb.

Building it

Step 1: Clean the method key before grouping

The log stores 'get' and 'post' alongside 'GET' and 'POST'. HTTP verbs are case-insensitive, so UPPER(method) in a normalized_calls CTE folds them together before any group forms. If you uppercase in the final SELECT instead, the groups were already split, and you get two 'GET' rows.

Step 2: Count each pairing

GROUP BY method, status over the cleaned rows gives COUNT(*) per pairing. That is the numerator, and it is the easy half.

Step 3: Bring the grand total back with a window

SUM(COUNT(*)) OVER () is evaluated after grouping. It sums the per-pairing counts across the whole grouped result, so every row carries 670 without a second pass over api_calls. The empty OVER () makes one frame that spans every group.

Step 4: Keep the division fractional

COUNT(*) and the windowed sum are both integers, and 484 divided by 670 in integer arithmetic is 0. Casting the numerator with CAST(COUNT(*) AS REAL) makes the whole ratio real, so 'GET' with 200 comes out at about 0.722.

Share of traffic per method and status
WITH normalized_calls AS (
    SELECT UPPER(method) AS method, status
    FROM api_calls
)
SELECT
    method,
    status,
    CAST(COUNT(*) AS REAL) / SUM(COUNT(*)) OVER () AS fraction
FROM normalized_calls
GROUP BY method, status
ORDER BY fraction DESC
Scalar subquery total
COUNT(*) * 1.0 / (SELECT COUNT(*) FROM api_calls) is also correct, and the subquery runs once. But it reads the base table a second time, and any filter later added to the outer query has to be copied into it, or the shares stop summing to 1.
Window over grouped counts
SUM(COUNT(*)) OVER () sums exactly the groups the query produced, so a filter added to normalized_calls moves the numerator and the denominator together. One scan, and one place to change.

Common follow-up questions