Content Session Counts

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
L3
Asked in
Meta

Problem

The content team's session-engagement report shows '/Home' and '/home' as two separate pages, because page_views logs the same URL in more than one letter case. Rebuild it with each page counted once regardless of case, showing how many unique sessions sit behind it, most sessions first. A view records only its user and never its session, so a page is credited with every session in user_sessions belonging to a user who viewed it.

Session metrics, content item by item.

Tables

page_views

Columns: view_id INTEGER, page_url TEXT, user_id INTEGER, referrer TEXT, dur_ms INTEGER, device TEXT, viewed_at TEXT

page_views: 10 of 200 rows
view_idpage_urluser_idreferrerdur_msdeviceviewed_at
5053/products1555amazon.com637desktop2026-09-02 01:09:00
5106/checkout1652twitter.com774tablet2026-09-03 02:18:00
5159/profile1749github.com911Mobile2026-09-04 03:27:00
5212/search1846NULL1048DESKTOP2026-09-05 04:36:00
5265/about1943reddit.com1185mobile2026-09-06 05:45:00
5318/blog/intro1555NULLNULLdesktop2026-09-07 06:54:00
5371/pricing1652bing.com1459tablet2026-09-08 07:03:00
5424/docs/api1749news.ycombinator.com1596Mobile2026-09-09 08:12:00
5477/settings1846NULL1733DESKTOP2026-09-10 09:21:00
5530/Home1943bing.com1870mobile2026-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

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 12 rows
page_urlunique_sessions
/products58
/about38
/profile35
/home35
classic_editor30

Worked solution and explanation

What this really is

This is a many-to-many fan-out in a content-analytics costume, with a dirty grouping key on top. page_views has no session_id, so the only road to sessions is user_id, and that key repeats on both sides of the join. Anyone can write the join. What separates candidates is what they count after it: COUNT(*) multiplies every session by each view its user left on the page, so tens of sessions turn into hundreds and the order tilts toward heavy viewers. The second trap is quieter. '/Home' and '/home' are one page, and a candidate who fixes the case after counting double-counts every session that reached both spellings.

Step 1: Normalize the page before you group

page_url holds '/Home' and '/home', '/PRODUCTS' and '/products'. Group on LOWER(pv.page_url) so each page lands in one bucket before anything is counted. Lowercasing only the output column is too late, because the groups have already split.

Step 2: Bridge through user_id

Inner-join user_sessions on user_id. A view records no session and the statement promises no time match, so every session of a viewer is credited to the page. Views with a NULL user_id drop out, which is right, since an anonymous view belongs to nobody's session.

Step 3: Count each session once per page

COUNT(DISTINCT us.session_id) collapses the fan-out. Because both spellings now share a group, a session reached through '/PRODUCTS' and '/products' is counted once, not twice.

Step 4: Sort by the session count

ORDER BY unique_sessions DESC puts the strongest pages on top. Pages tied on a count may come back in any order, and the grader accepts that.

Unique sessions per page, case-normalized
SELECT
    LOWER(pv.page_url) AS page_url,
    COUNT(DISTINCT us.session_id) AS unique_sessions
FROM page_views pv
JOIN user_sessions us ON us.user_id = pv.user_id
GROUP BY LOWER(pv.page_url)
ORDER BY unique_sessions DESC
Summing the spellings afterwards
'/products' has 35 sessions and '/PRODUCTS' has 38, so adding the two finished rows reports 73.
Normalizing before counting
Grouping on LOWER(pv.page_url) lets COUNT(DISTINCT us.session_id) see both spellings at once. Three users viewed both, so the page really has 58 sessions.
The inflated version
SELECT
    LOWER(pv.page_url) AS page_url,
    COUNT(*) AS unique_sessions
FROM page_views pv
JOIN user_sessions us ON us.user_id = pv.user_id
GROUP BY LOWER(pv.page_url)
ORDER BY unique_sessions DESC

Runs, looks plausible, and counts joined rows, which are not sessions.

Common follow-up questions