Session Duration by Account Status
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
- Asked in
- phData
Problem
Session length is logged per session in user_sessions while account status lives only on each person's users record, and a few sessions were never timed, so a blank duration is unknown rather than zero. An analyst checking whether account tier tracks engagement wants the average session duration in seconds for every account status.
Average session duration broken down by user account status
Tables
users
Columns: user_id INTEGER, username TEXT, email TEXT, signup_date TEXT, account_status TEXT, age_bucket TEXT
| user_id | username | signup_date | account_status | age_bucket | |
|---|---|---|---|---|---|
| 100 | alice | alice@example.com | 2025-02-02 | inactive | 25-34 |
| 197 | aaron42 | aaron42@example.com | 2026-03-03 | suspended | 35-44 |
| 294 | amelia | amelia@example.com | 2024-04-04 | pending_verification | 45-54 |
| 391 | arjun | arjun@example.com | 2025-05-05 | active | 55-64 |
| 488 | ava99 | ava99@example.com | 2026-06-06 | inactive | 65+ |
| 585 | andrew_k | andrew_k@example.com | 2024-07-07 | suspended | NULL |
| 682 | anika | anika@example.com | 2025-08-08 16:34:00 | pending_verification | 18-24 |
| 779 | aiden | aiden@example.com | 2026-09-09 14:53:00 | active | 25-34 |
| 876 | aria_b | aria_b@example.com | 2024-10-10 | inactive | 35-44 |
| 973 | brian | brian@example.com | 2025-11-11 | suspended | 45-54 |
user_sessions
Columns: session_id INTEGER, user_id INTEGER, device_id INTEGER, session_start TEXT, session_duration_sec INTEGER, pages_viewed INTEGER
| session_id | user_id | device_id | session_start | session_duration_sec | pages_viewed |
|---|---|---|---|---|---|
| 7061 | 197 | 230 | 2026-02-02 01:00:00 | 53 | 3 |
| 7122 | 294 | 259 | 2026-03-03 02:00:00 | 76 | 6 |
| 7183 | 391 | 288 | 2026-04-04 03:00:00 | 99 | 9 |
| 7244 | 488 | 317 | 2026-05-05 04:00:00 | 122 | 12 |
| 7305 | 585 | 346 | 2026-06-06 05:00:00 | 145 | 15 |
| 7366 | 682 | 375 | 2026-07-07 06:00:00 | 168 | 18 |
| 7427 | 779 | 404 | 2026-08-08 07:00:00 | 191 | 21 |
| 7488 | 876 | NULL | 2026-09-09 08:00:00 | 214 | 24 |
| 7549 | 973 | 462 | 2026-01-10 09:00:00 | 237 | 27 |
| 7610 | 1070 | 491 | 2026-02-11 10:00:00 | 260 | 30 |
Expected output
| account_status | avg_session_duration |
|---|---|
| active | 1278.0625 |
| inactive | 1663.1707317073171 |
| pending_verification | 1319.695652173913 |
| suspended | 1355.3333333333333 |
Worked solution and explanation
This is a cross-table average dressed up as an engagement study. The metric, session_duration_sec, sits on user_sessions; the bucket, account_status, exists only on users. Everyone writes the join. What separates candidates is what they do with the sessions that were never timed: 17 of the 200 sessions carry a NULL duration, and the reflex to tidy them with COALESCE(session_duration_sec, 0) turns unknown sessions into zero-second ones. Every status average drops, by an amount that depends on how many untimed sessions that status happens to hold, so part of the tier gap the analyst is chasing becomes an artifact of missing data.
Getting to the answer
Step 1: Find where each piece lives
user_sessions has the duration and a user_id, but no status. No filter or projection over sessions alone can bucket them by tier, so you bring in users on user_id. Because user_id is unique in users, each session matches exactly one user: the join adds a column without multiplying sessions.
Step 2: Average sessions, not users
GROUP BY u.account_status with AVG(us.session_duration_sec) weights every session once. Averaging per user first and then across users gives a person with one session the same say as someone with eight, which answers a different question.
Step 3: Let the average skip untimed sessions
AVG drops NULLs from both the sum and the count, which is exactly what 'unknown, not zero' means. The correct handling of the blanks is to add nothing.
SELECT u.account_status, AVG(us.session_duration_sec) AS avg_session_duration
FROM users u
INNER
JOIN user_sessions us ON u.user_id = us.user_id
GROUP BY u.account_statusSELECT u.account_status, AVG(COALESCE(us.session_duration_sec, 0)) AS avg_session_duration
FROM users u
JOIN user_sessions us ON u.user_id = us.user_id
GROUP BY u.account_statusCOALESCE keeps the untimed sessions in the denominator at zero seconds, so every status average comes out low.
Common follow-up questions
- How would you also report how many sessions in each status had no recorded duration? (Tests whether the candidate knows `COUNT(*)` and `COUNT(session_duration_sec)` differ exactly by the NULLs.)
- The analyst now wants the median duration per status instead of the mean. What changes? (Probes percentile aggregates and whether NULLs are still excluded.)
- If `users` kept a row per status change, how would you attribute each session to the status in force at `session_start`? (Tests a range join against a slowly changing dimension.)