Unique Visitors
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
List the calendar months in user_sessions where someone came back, showing each month's number of different users next to its number of sessions. An analyst auditing the board's monthly active users report wants only the months where at least one visitor started more than one session, since those are the months where raw activity overstates the audience.
Which months actually had an audience?
Tables
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
| activity_month | unique_visitors | session_count |
|---|---|---|
| 2023-01 | 5 | 8 |
| 2023-05 | 5 | 7 |
| 2023-09 | 5 | 7 |
| 2024-04 | 5 | 7 |
| 2026-06 | 15 | 17 |
Worked solution and explanation
What this really is
This is a people-versus-events question wearing a board-report costume. Everyone buckets session_start into months and counts something. What separates candidates is holding two different counts of the same rows at once: sessions and the people behind them. The months the analyst wants are exactly the ones where those numbers disagree, so a query that counts rows on both sides compares a number with itself and returns nothing.
Step 1: Collapse timestamps to a calendar month
STRFTIME('%Y-%m', session_start) turns every session into its year-month label, so March 2025 and March 2026 stay separate months. Group by that expression, not by the raw timestamp, or every session becomes its own group.
Step 2: Count people and sessions side by side
COUNT(DISTINCT user_id) is the audience; COUNT(*) is the activity. Return both, because the gap between them is the whole story the report needs to see.
Step 3: Keep months where activity outran the audience
A month has a returning visitor exactly when it has more sessions than different users. That comparison is about the grouped result, so it lives in HAVING COUNT(*) > COUNT(DISTINCT user_id). Drop the DISTINCT there and the condition can never be true.
The solution
SELECT
STRFTIME('%Y-%m', session_start) AS activity_month,
COUNT(DISTINCT user_id) AS unique_visitors,
COUNT(*) AS session_count
FROM user_sessions
GROUP BY STRFTIME('%Y-%m', session_start)
HAVING COUNT(*) > COUNT(DISTINCT user_id)- Counts rows twice
HAVING COUNT(*) > COUNT(user_id)compares session rows with session rows.user_idis never empty, so the two are always equal and every month is dropped.- Counts people once
HAVING COUNT(*) > COUNT(DISTINCT user_id)counts each person once per month, so it is true exactly when someone opened a second session that month.
Common follow-up questions
- How would you report months that fell below the bar as zero instead of dropping them? (Needs a calendar of months joined to the counts so empty and thin months still appear.)
- What changes if `session_start` is stored in local time across regions? (Normalizing to one zone before bucketing, or a session near midnight lands in the wrong month.)
- Finance wants daily, weekly and monthly actives from one table. What do you precompute? (Unique counts do not add up across days, so a per-user-per-day rollup or a mergeable sketch is needed.)