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

user_sessions: 10 of 200 rows
session_iduser_iddevice_idsession_startsession_duration_secpages_viewed
70611972302026-02-02 01:00:00533
71222942592026-03-03 02:00:00766
71833912882026-04-04 03:00:00999
72444883172026-05-05 04:00:0012212
73055853462026-06-06 05:00:0014515
73666823752026-07-07 06:00:0016818
74277794042026-08-08 07:00:0019121
7488876NULL2026-09-09 08:00:0021424
75499734622026-01-10 09:00:0023727
761010704912026-02-11 10:00:0026030

Expected output

First 5 of 40 rows
user_idavg_durationsession_countlongest
100312077200
8442198232912
8345195132881
1943138752307
876113442054

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.

Per-user session summary
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. AVG and MAX come out right, but COUNT(*) counts every row, so a user with two timed sessions and one blank one reads as 3 and passes HAVING COUNT(*) >= 3. That is eight extra users on this data, and session_count is too high for five of the real ones.
Row filter in place
WHERE session_duration_sec IS NOT NULL removes 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