Activity Histogram
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
A user's activity level is the number of sessions they have in user_sessions, and a session with no device recorded still counts toward it. Build the histogram of those levels, showing how many users land on each one, lowest level first.
How many users did X things? Build the distribution.
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
| session_count | user_count |
|---|---|
| 1 | 9 |
| 2 | 2 |
| 3 | 28 |
| 5 | 19 |
| 8 | 1 |
Worked solution and explanation
This is a histogram of a histogram dressed up as retention research. Everyone writes the first pass: GROUP BY user_id with COUNT(*), one row per user. The skill being probed is noticing that this is only the input. The question asks how many users share each count, so the count you just computed has to become the grouping key of a second pass. Stop after one pass and you hand back 59 rows of user ids instead of five bars, and the shape the team wanted to see never appears.
Two passes, two grains
Step 1: Collapse sessions to one row per user
GROUP BY user_id with COUNT(*) turns 200 sessions into 59 users, each carrying a session_count. Count rows, not a column: 23 sessions have a NULL device_id, so COUNT(device_id) would quietly push some users down a level.
Step 2: Group on the count you just computed
In the second pass session_count stops being a measure and becomes the key. The outer GROUP BY session_count with COUNT(*) counts users per level: 9 users at one session, 28 at three, 19 at five, a single user at eight.
Step 3: Read it lowest level first
Sort by session_count ascending so the bars run left to right. Levels nobody reached (4, 6, 7) simply do not appear: a histogram of observed levels has no zero rows unless you generate them yourself.
WITH user_counts AS (
SELECT user_id, COUNT(*) AS session_count
FROM user_sessions
GROUP BY user_id
)
SELECT session_count, COUNT(*) AS user_count
FROM user_counts
GROUP BY session_count
ORDER BY session_countuser_counts is the first grain (one row per user); the outer query is the second (one row per level).
- One pass
SELECT user_id, COUNT(*) FROM user_sessions GROUP BY user_idreturns 59 rows keyed byuser_id. The numbers are right but they answer the wrong question: nobody can read a distribution off a list of users.- Two passes
- Feed that result into
GROUP BY session_countand the 59 rows collapse into 5 bars, one per level some user actually reached, withuser_countas the bar height.
Common follow-up questions
- How would you emit zero-user rows for levels like 4, 6 and 7 that nobody hit? (Tests generating a dense range up to `MAX(session_count)` and outer-joining the counts onto it.)
- Heavy users run to hundreds of sessions. How would you bucket levels into bands such as 1, 2-5, 6-20 and 21+? (Tests mapping the computed count onto bands before the second pass.)
- What changes if the team only wants sessions from 2026? (Tests that the filter belongs on raw sessions before the first pass, not on the finished histogram.)
- Can you also return each level's share of all users? (Tests a total computed over the grouped result, by a window or a scalar subquery.)