High Engagement Pages
A hard SQL interview practice problem on DataDriven. Write the query and run it against a real database, with instant feedback.
- Domain
- SQL
- Difficulty
- hard
- Seniority
- L3
- Asked in
- Meta
Problem
We run a web analytics product where every page view logs how long it stayed on screen, in milliseconds, and which user viewed it. For each session a user starts, add up the on-screen time in seconds across all of that user's page views.
Some pages hold attention longer than others.
Tables
page_views
Columns: view_id INTEGER, page_url TEXT, user_id INTEGER, referrer TEXT, dur_ms INTEGER, device TEXT, viewed_at TEXT
| view_id | page_url | user_id | referrer | dur_ms | device | viewed_at |
|---|---|---|---|---|---|---|
| 5053 | /products | 1555 | amazon.com | 637 | desktop | 2026-09-02 01:09:00 |
| 5106 | /checkout | 1652 | twitter.com | 774 | tablet | 2026-09-03 02:18:00 |
| 5159 | /profile | 1749 | github.com | 911 | Mobile | 2026-09-04 03:27:00 |
| 5212 | /search | 1846 | NULL | 1048 | DESKTOP | 2026-09-05 04:36:00 |
| 5265 | /about | 1943 | reddit.com | 1185 | mobile | 2026-09-06 05:45:00 |
| 5318 | /blog/intro | 1555 | NULL | NULL | desktop | 2026-09-07 06:54:00 |
| 5371 | /pricing | 1652 | bing.com | 1459 | tablet | 2026-09-08 07:03:00 |
| 5424 | /docs/api | 1749 | news.ycombinator.com | 1596 | Mobile | 2026-09-09 08:12:00 |
| 5477 | /settings | 1846 | NULL | 1733 | DESKTOP | 2026-09-10 09:21:00 |
| 5530 | /Home | 1943 | bing.com | 1870 | mobile | 2026-09-11 10:30:00 |
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
| session_id | total_seconds |
|---|---|
| 7061 | 41.456 |
| 7122 | 42.004 |
| 7244 | 43.1 |
| 7305 | 24.564 |
| 7366 | 44.196 |
Worked solution and explanation
What this problem is really asking
Beneath the analytics framing this is a cross-table sum: the value you group by, session_id, lives in user_sessions, while the thing you add up, dur_ms, lives in page_views, and the only column the two tables share is user_id. So the real question is how a page view maps to a session, and the honest answer is that it does not map to one directly. A page view is stamped with a user, not a session, so a session's viewing time is the total on-screen time of the user who owns that session, and every session that user started carries the same total.
The one thing to notice
There is no session_id on page_views. A candidate who goes hunting for one, or who tries to line a view up with a single session, stalls. The link is user_id: join user_sessions to page_views on it and every session a user started collects that user's whole set of views. That repetition is the intended grain here, not a bug to design around.
Building the query
Step 1: Join the two tables on the shared user
INNER JOIN page_views pv ON pv.user_id = us.user_id attaches every one of a user's views to each of that user's sessions. Because it is an inner join, a session whose user logged no views quietly drops out, which is exactly the membership you want: a session only counts once there is on-screen time behind it.
Step 2: Total the on-screen time per session in seconds
Group by us.session_id and compute SUM(pv.dur_ms) / 1000.0. Summing the raw millisecond durations and dividing once at the end gives seconds; the / 1000.0 keeps the result a real number instead of integer-truncating. The roughly 1% of dur_ms that are NULL are ignored by SUM, which is the behavior you want.
The solution
SELECT us.session_id, SUM(pv.dur_ms) / 1000.0 AS total_seconds
FROM user_sessions us
INNER JOIN page_views pv ON pv.user_id = us.user_id
GROUP BY us.session_idCommon follow-up questions
- How would you report each user's total viewing time exactly once, rather than repeated across every session they started? (Tests whether the candidate understands the user-to-session grain and can avoid double counting.)
- About 1% of `dur_ms` values are `NULL`. What does `SUM(dur_ms)` do with them, and when would that be the wrong choice? (Tests understanding of how NULLs behave inside aggregate functions.)
- What changes in the result if you switch the `INNER JOIN` to a `LEFT JOIN` from `user_sessions`? (Tests awareness of inner versus outer join semantics on the membership edge.)