The Loudest Rooms
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
We run a community app where the people who post in chat channels are the same people we serve ads to, and a channel is credited with a poster's ad impressions once for every message they sent in it. Find each channel's impression count from ad_impressions and chat_msgs, busiest first.
Every channel has an audience. Find where it is biggest.
Tables
ad_impressions
Columns: impression_id INTEGER, user_id INTEGER, ad_campaign TEXT, impression_time TEXT, clicked INTEGER, revenue REAL
| impression_id | user_id | ad_campaign | impression_time | clicked | revenue |
|---|---|---|---|---|---|
| 253 | 100 | BRAND_AWARENESS_Q1 | 2026-02-02 01:07:00 | 0 | NULL |
| 306 | 197 | PRODUCT_LAUNCH_X | 2026-03-03 02:14:00 | 0 | NULL |
| 359 | 294 | RETARGETING_CART | 2026-04-04 03:21:00 | 0 | NULL |
| 412 | 391 | HOLIDAY_PROMO | 2026-05-05 04:28:00 | 0 | NULL |
| 465 | 488 | NEW_USER_ACQU | 2026-06-06 05:35:00 | 1 | 0.2 |
| 518 | NULL | LOYALTY_PROGRAM | 2026-07-07 06:42:00 | 0 | NULL |
| 571 | 682 | FLASH_SALE_48H | 2026-08-08 07:49:00 | 0 | NULL |
| 624 | 779 | SUMMER_SALE_2024 | 2026-09-09 08:56:00 | 0 | NULL |
| 677 | 876 | BRAND_AWARENESS_Q1 | 2026-01-10 09:03:00 | 0 | NULL |
| 730 | 973 | PRODUCT_LAUNCH_X | 2026-02-11 10:10:00 | 1 | 0.35 |
chat_msgs
Columns: msg_id INTEGER, channel TEXT, sender_id INTEGER, content TEXT, msg_type TEXT, sent_at TEXT, edited INTEGER, reply_to INTEGER
| msg_id | channel | sender_id | content | msg_type | sent_at | edited | reply_to |
|---|---|---|---|---|---|---|---|
| 147 | #engineering | 197 | Anyone seeing increased latency? | file | 2026-02-02 01:07:00 | 0 | NULL |
| 194 | #incidents | 294 | PR #412 ready for review | reaction | 2026-03-03 02:14:00 | 0 | NULL |
| 241 | #random | 391 | Dashboard is down | thread_reply | 2026-04-04 03:21:00 | 0 | NULL |
| 288 | #deployments | 488 | Fixed the flaky test | system | 2026-05-05 04:28:00 | 0 | NULL |
| 335 | #data-team | 585 | Merged the migration | Text | 2026-06-06 05:35:00 | 0 | NULL |
| 382 | #general | 682 | NULL | FILE | 2026-07-07 06:42:00 | 0 | NULL |
| 429 | #engineering | 779 | LGTM | text | 2026-08-08 07:49:00 | 0 | NULL |
| 476 | #incidents | 876 | 👍 | file | 2026-09-09 08:56:00 | 0 | 5 |
| 523 | #random | 973 | reaction | 2026-01-10 09:03:00 | 0 | NULL | |
| 570 | #deployments | 1070 | Can someone check the pipeline? | thread_reply | 2026-02-11 10:10:00 | 0 | NULL |
Expected output
| channel | impression_count |
|---|---|
| #deployments | 152 |
| #incidents | 148 |
| #random | 120 |
| #data-team | 102 |
| #general | 100 |
Worked solution and explanation
What this really is
Strip the community-app costume and this is a many-to-many attribution. Impressions live in ad_impressions with no channel. Channels live in chat_msgs with no impressions. The only bridge is one person id that goes by two names, user_id and sender_id. Anyone can write that join. The trap is assuming channels split the audience into separate buckets. On this data every poster talks in exactly two channels, with two messages in each. So a single impression legitimately lands in two channels and is credited twice in each. Treat the channels as a partition, or dedupe impressions before attributing them, and your six counts sum to 180 instead of 720, with every channel undercounted.
Step 1: Anchor on ad_impressions
The thing being counted is an impression, so start there. The channel is not on this table, so you have to reach it through the person who saw the ad.
Step 2: Bridge on the person id
Inner join chat_msgs on ai.user_id = cm.sender_id. The 20 impressions with a NULL user_id match nothing and drop out. That is right: an impression with no viewer reached no channel's posters.
Step 3: Count at the join grain
Group by cm.channel and take COUNT(*). Every sender here has two messages in each of their two channels, so each of their impressions shows up twice per channel. That is the metric itself: a channel is credited once for every message the viewer posted there.
Step 4: Sort busiest first
Order by impression_count descending. The six counts are all different on this data, from 152 down to 98, so there is nothing to break. If two did tie, either order would still be busiest first.
SELECT cm.channel AS channel,
COUNT(*) AS impression_count
FROM ad_impressions AS ai
INNER JOIN chat_msgs AS cm
ON ai.user_id = cm.sender_id
GROUP BY cm.channel
ORDER BY impression_count DESC;Inner join on the shared person id, one count per channel, busiest first.
- `COUNT(*)`
- Counts joined rows: one per impression per message the viewer sent in that channel. '#deployments' lands at 152, the message-credited count the statement defines.
- `COUNT(DISTINCT ai.impression_id)`
- Counts each impression once per channel and returns 76 for '#deployments'. It answers 'how many ads reached this audience', which is a different question from the one asked here.
Common follow-up questions
- Product now wants each impression counted once per channel, however often the viewer posts there. What changes? (Moves from `COUNT(*)` to counting unique `impression_id` values per channel.)
- Rewrite it to count only clicked impressions per channel. (Adds a `clicked = 1` filter before the aggregate.)
- How would you also return channels whose posters saw no ads at all? (Forces a `chat_msgs`-anchored left join and a count of `ai.impression_id` instead of `COUNT(*)`.)