Session Pulse
An easy SQL interview practice problem on DataDriven. Write the query and run it against a real database, with instant feedback.
- Domain
- SQL
- Difficulty
- easy
- Seniority
- L3
Problem
In user_sessions each row is one session, but some sessions have no duration on file and are left out of every figure. From the rest, summarize each user's engagement as their average session length, how many sessions they logged and their longest one, keeping users with at least three sessions, longest average first.
Engagement is slipping. Who is phoning it in?
Tables
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
| user_id | avg_duration | session_count | longest |
|---|---|---|---|
| 100 | 3120 | 7 | 7200 |
| 8442 | 1982 | 3 | 2912 |
| 8345 | 1951 | 3 | 2881 |
| 1943 | 1387 | 5 | 2307 |
| 876 | 1134 | 4 | 2054 |
Worked solution and explanation
Two filters, two levels
This is a filter-scoping question dressed up as an engagement summary. Every candidate writes the per-user average, count and max. What separates them is where each rule lives. The missing-duration rule is about individual sessions, so it has to act before anything is summarized. The three-session floor is about a user's total, so it can only be judged afterwards. Trust AVG to skip the NULLs and leave the rows in, and COUNT(*) still counts the untimed sessions: eight users with only two real sessions get past the floor, and the answer has 48 rows where it should have 40.
Step 1: Drop untimed sessions before anything is counted
WHERE session_duration_sec IS NOT NULL runs once per row, before grouping, so AVG, COUNT(*) and MAX all see only timed sessions. AVG and MAX would ignore the NULL anyway. COUNT(*) would not, and the count is exactly what the floor reads.
Step 2: Collapse to one row per user_id
GROUP BY user_id gives one bucket per user, and the three figures come out of the same pass: AVG(session_duration_sec) as avg_duration, COUNT(*) as session_count, MAX(session_duration_sec) as longest.
Step 3: Judge the floor on the user's tally
Three sessions is a property of the bucket, not of any one row, so it goes in HAVING COUNT(*) >= 3. Because the row filter already ran, that count is timed sessions only, which is what the floor means.
Step 4: Sort by the average, longest first
ORDER BY avg_duration DESC sorts on the alias the query returns. Users whose averages tie may come back in either order; the grade only checks the sort.
SELECT user_id, AVG(session_duration_sec) AS avg_duration, COUNT(*) AS session_count, MAX(session_duration_sec) AS longest
FROM user_sessions
WHERE session_duration_sec IS NOT NULL
GROUP BY user_id
HAVING COUNT(*) >= 3
ORDER BY avg_duration DESC- Row filter left out
- No
WHERE.AVGandMAXcome out right, butCOUNT(*)counts every row, so a user with two timed sessions and one blank one reads as 3 and passesHAVING COUNT(*) >= 3. That is eight extra users on this data, andsession_countis too high for five of the real ones. - Row filter in place
WHERE session_duration_sec IS NOT NULLremoves the blank rows first, so all three aggregates and the floor agree on the same set of sessions. 40 users qualify.
Common follow-up questions
- User 100 has three sessions on overlapping devices the same afternoon. How would you report engaged time instead of summed session length? (Tests interval merging on `session_start` plus `session_duration_sec`.)
- Product wants users under the floor kept, but flagged rather than dropped. What changes? (Tests moving a group filter into a computed column.)
- The floor becomes three timed sessions in the last 30 days. Where does that window go? (Tests that a time window is a row filter, applied before the tally.)