Session Overview

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
L4

Problem

Accounts that never logged a session still belong in the report with a count of zero, and every account is its own row even where two usernames differ only in letter case. For each user in users, the product team wants the user_id and username next to how many sessions that user has in user_sessions and their longest session_duration_sec.

Full engagement picture, even for the ones who never showed up.

Tables

users

Columns: user_id INTEGER, username TEXT, email TEXT, signup_date TEXT, account_status TEXT, age_bucket TEXT

users: 10 of 200 rows
user_idusernameemailsignup_dateaccount_statusage_bucket
100alicealice@example.com2025-02-02inactive25-34
197aaron42aaron42@example.com2026-03-03suspended35-44
294 ameliaamelia@example.com2024-04-04pending_verification45-54
391arjun arjun@example.com2025-05-05active55-64
488ava99ava99@example.com2026-06-06inactive65+
585andrew_kandrew_k@example.com2024-07-07suspendedNULL
682anikaanika@example.com2025-08-08 16:34:00pending_verification18-24
779aidenaiden@example.com2026-09-09 14:53:00active25-34
876aria_baria_b@example.com2024-10-10inactive35-44
973brianbrian@example.com2025-11-11suspended45-54

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 200 rows
user_idusernamesession_countlongest_session_sec
100alice87200
197aaron4251893
294 amelia51916
391arjun 51939
488ava9951502

Worked solution and explanation

What this problem really is

This is an outer-join aggregate wearing an engagement-report costume, and it has two traps stacked on top of each other. Everyone writes the join and the COUNT. What separates candidates is whether the users with zero sessions survive, and whether they come out as 0 or as 1. An INNER JOIN quietly deletes every session-less account, so 'abel' never appears. A LEFT JOIN with COUNT(*) keeps 'abel' but reports one session, because the outer join manufactured a row for him. Either way, the inactive accounts Product explicitly asked about are exactly the rows that come out wrong.

There is a third, quieter trap in the grain. The users table holds 'alice' and 'Alice' as two different accounts with different user_id values. Collapse on username and those two people blur into whatever your engine's string comparison decides. The account is the user_id; the name is just a label you carry along.

Step 1: Drive the query from users

The report is about every account, so users is the preserved side. Put it first and LEFT JOIN user_sessions on user_id. Accounts with no sessions come through once, with every s. column NULL.

Step 2: Collapse on user_id, carry username

Group by u.user_id, u.username. The user_id is what makes each row one account; username rides along because it is functionally determined by the key. This is what keeps 'alice' and 'Alice' as two rows instead of one muddled one.

Step 3: Count the joined column, not the row

COUNT(s.session_id) skips NULLs, so the phantom row an unmatched user gets contributes nothing and the count is 0. COUNT(*) counts that phantom row and reports 1. Same query shape, one token different, opposite answer for the population that matters.

Step 4: Let MAX return NULL for nobody

MAX(s.session_duration_sec) ignores NULLs too, so a user with no sessions gets NULL, which is the honest answer: there is no longest session. Do not paper over it with 0; a zero-second session is a real event, and a dashboard cannot tell the two apart.

Every account, its session count and its longest session
SELECT
    u.user_id,
    u.username,
    COUNT(s.session_id) AS session_count,
    MAX(s.session_duration_sec) AS longest_session_sec
FROM users u
LEFT JOIN user_sessions s
    ON s.user_id = u.user_id
GROUP BY u.user_id, u.username
COUNT(*) after the outer join
Every unmatched user gets one all-NULL row from the LEFT JOIN, and COUNT(*) counts it. 'abel' shows session_count 1 with a NULL longest session: a contradiction sitting right in the output.
COUNT of the joined key
COUNT(s.session_id) only counts rows where a session actually matched. 'abel' shows 0 and NULL, which is what happened.

Common follow-up questions