Last Seen

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
Asked in
Moore Capital Management

Problem

Our recency model keeps scoring users on stale activity because it reads every row of user_sessions rather than one latest start per user. Give each user's most recent session start, from the lowest user ID up to the highest.

Everyone has a most recent session.

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 59 rows
user_idlatest_session_start
1002026-09-01 08:00:00
1972026-08-06 13:00:00
2942026-09-07 14:00:00
3912026-08-16 19:00:00
4882026-09-05 20:00:00

Worked solution and explanation

What this really is

Under the retention costume this is a grain collapse. user_sessions holds one row per session, the model wants one row per user, and the only thing that survives the collapse is the freshest session_start. Anyone can find a latest timestamp. What separates candidates is whether they fold the group or just trim it. SELECT DISTINCT user_id, session_start looks tidy, but it still returns one row per session. A user with eight sessions shows up eight times, and the recency model joins on eight 'latest' dates. The opposite slip, MAX with no grouping, returns one global date for the whole table.


Walking through it

Step 1: Fold every session into its user

GROUP BY user_id is what makes the output one row per user. Without it the aggregate spans all of user_sessions. With DISTINCT instead, you only remove exact repeated pairs, so every session that started at a different moment survives.

Step 2: Take the latest start inside each group

MAX(session_start) returns each user's freshest start. session_start is TEXT, but it is stored as zero-padded 'YYYY-MM-DD HH:MM:SS', so string order is time order and no parsing is needed.

Step 3: List users from the lowest ID up

The roster is read lowest user_id first, and that order is part of the answer, so ORDER BY user_id stays. The grouping leaves exactly one row per user_id, so the sort has no ties to break.


The solution

MAX aggregation per user
SELECT user_id, MAX(session_start) AS latest_session_start
FROM user_sessions
GROUP BY user_id
ORDER BY user_id
Trims the rows
SELECT DISTINCT user_id, session_start removes only repeated pairs. Every session with its own start survives, so the result keeps the session grain.
Folds the group
GROUP BY user_id with MAX(session_start) turns each user's sessions into one row, so the result has exactly one row per user: 59 on this data.

Common follow-up questions