All Known Endpoints

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
L3

Problem

The platform team is assembling a service catalog of every endpoint that appears in either the API request logs or the rate limit rules. Authentication has several sub-routes nested under /api/v1/auth, but the catalog should show the auth service just once as /api/v1/auth; likewise, paths that differ only by a trailing slash are the same endpoint. Return the full list of endpoints.

Two tables. One truth. Every endpoint accounted for.

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

rate_limits

Columns: limit_id INTEGER, client TEXT, endpoint TEXT, allowed INTEGER, blocked INTEGER, window TEXT, checked TEXT

rate_limits: 10 of 200 rows
limit_idclientendpointallowedblockedwindowchecked
129client-002/api/v1/search107NULL5m2026-02-02 01:03:00
158client-003/api/v1/orders114NULL15m2026-03-03 02:06:00
187mobile-app/api/v2/analytics121NULL1h2026-04-04 03:09:00
216partner-api/api/v1/auth128121d2026-05-05 04:12:00
245internal-svc/api/v1/payments135NULL1m2026-06-06 05:15:00
274batch-worker/api/v1/users142NULL5m2026-07-07 06:18:00
303admin-ui/api/v1/search149NULL15m2026-08-08 07:21:00
332webhook-svc/api/v1/orders156241h2026-09-09 08:24:00
361client-010/api/v2/analytics163NULL1d2026-01-10 09:27:00
390client-001/api/v1/auth170NULL1m2026-02-11 10:30:00

Expected output

First 5 of 7 rows
endpoint
/api/v1/orders
/api/v2/products
/api/v1/search
/api/v1/auth
/api/v2/analytics

Worked solution and explanation

What this really is

Strip the service-catalog costume and this is a set union with two namespace roll-ups hiding inside it. The two tables each carry an endpoint column; the ask is the union of those two sets. Anyone can write that union. The trick is what counts as one endpoint. /api/v1/auth, /api/v1/auth/login and /api/v1/auth/logout are the same auth service written three ways, and /api/v1/users and /api/v1/users/ are the same service separated only by a trailing slash. A plain dedup keeps all of them. You have to rewrite each raw path to its canonical form BEFORE the dedup, on both axes. Miss either one and your catalog ships nine or ten endpoints where the truth is seven.


Walk it

Step 1: Pool both sources without losing rows

Stack the endpoint column from api_calls and rate_limits with UNION ALL, not UNION, and TRIM off any stray whitespace as you go. You are going to dedup on the fully rolled-up value anyway, so paying for a distinct pass here on the raw strings is wasted work and, worse, it dedups before the normalizations can do their job.

Step 2: Strip the trailing slash

Fold the trailing-slash twins together. When an endpoint ends in a slash (LIKE '%/'), drop that last character with SUBSTR(endpoint, 1, LENGTH(endpoint) - 1); otherwise leave it alone. Now /api/v1/users/ and /api/v1/users are the same string, and /api/v2/products/ collapses into /api/v2/products. Skip this and those twins each keep a separate catalog row.

Step 3: Roll auth sub-routes up to the service

Roll auth sub-routes up to the service. Wrap the cleaned path in a CASE: when it matches LIKE '/api/v1/auth/%', rewrite it to '/api/v1/auth'; otherwise leave it. The slash right before the wildcard is the quiet correctness detail: it matches only nested sub-routes, so the bare /api/v1/auth service passes through untouched. Drop that slash (LIKE '/api/v1/auth%') and you would also swallow anything like /api/v1/authorize, misgrouping unrelated services.

Step 4: Collapse and order

Apply DISTINCT to the fully normalized expression so the auth sub-routes and the slash twins each fold into one row, then ORDER BY the result ascending. The sort runs on the normalized string, so each service sorts in exactly one place.

The solution

Union, strip the slash, roll up auth, dedup, order
SELECT DISTINCT
    CASE
        WHEN service LIKE '/api/v1/auth/%'
            THEN '/api/v1/auth'
        ELSE service
    END AS endpoint
FROM (
    SELECT
        CASE
            WHEN cleaned LIKE '%/'
                THEN SUBSTR(cleaned, 1, LENGTH(cleaned) - 1)
            ELSE cleaned
        END AS service
    FROM (
        SELECT TRIM(endpoint) AS cleaned FROM api_calls
        UNION ALL
        SELECT TRIM(endpoint) AS cleaned FROM rate_limits
    )
)

The inner query folds the trailing-slash twins; the outer CASE rolls up auth; DISTINCT then collapses to one row per real service.

Plain UNION
SELECT endpoint FROM api_calls UNION SELECT endpoint FROM rate_limits. Dedups on the raw string, so /api/v1/auth, /api/v1/auth/login, /api/v1/auth/logout, /api/v1/users and /api/v1/users/ all survive. Returns nine or ten rows: several are the same service written multiple ways.
Normalize then dedup
Strip the trailing slash and roll auth sub-routes up inside CASEs, then DISTINCT over that. The variants normalize to a single canonical form and fold together. Returns the correct 7 rows, one per real service.

Common follow-up questions