Frequent Message Senders
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
- Amazon
Problem
Our anomaly-detection team is hunting accounts that flooded the chat platform this year, where only messages carrying actual text count as real activity (blank posts are just noise). Surface every sender behind more than two such messages, along with that count.
Someone is sending too many messages.
Tables
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
| sender_id | msg_count |
|---|---|
| 100 | 4 |
| 585 | 4 |
| 1070 | 4 |
| 1555 | 4 |
| 2040 | 4 |
Worked solution and explanation
What this is really asking
This is a two-phase filter wearing an anomaly-detection costume. You filter rows FIRST (keep only this year's messages that carry text), collapse them to one row per sender_id, count what survived, then filter the GROUPS down to those above two. A single WHERE cannot say 'more than two surviving messages' because that count does not exist until the rows are grouped. The trap is knowing which filter runs at which phase: mix them up and you either crash or count the wrong rows.
Break it down
Step 1: Filter the rows in WHERE
content <> '' drops empty and null content (in SQL, NULL <> '' is not true, so those rows fall out too), and sent_at >= the start of the year drops older messages. This runs before grouping, so only qualifying rows ever reach the count.
Step 2: Aggregate per sender
GROUP BY sender_id with COUNT(*) yields one row per sender carrying their surviving message total across every channel. It is COUNT(*), not COUNT(content): after the WHERE there are no nulls left, and every remaining row is one real message.
Step 3: Filter the groups in HAVING
The more-than-two test reads COUNT(*), which does not exist until the group forms, so it belongs in HAVING. A sender with only one or two qualifying messages this year is dropped here, leaving just the accounts that are actually flooding.
Step 4: Order busiest first
ORDER BY msg_count DESC puts the loudest senders first. Counts tie (many senders share the same total), so sender_id ascending is the tiebreaker that makes the order total and reproducible.
SELECT
sender_id,
COUNT(*) AS msg_count
FROM chat_msgs
WHERE content <> ''
AND sent_at >= '2026-01-01'
GROUP BY sender_id
HAVING COUNT(*) > 2
;COMMON FOLLOW-UP QUESTIONS
Common follow-up questions
- How would you narrow this to just the last 24 hours? (Tighten the WHERE to `sent_at` >= NOW() - INTERVAL '1 day' so the partition key prunes the scan even harder.)
- What if we only care about senders who flood a single channel? (Add channel to the grouping: GROUP BY `sender_id`, channel HAVING COUNT(*) > 2. The grain changes to sender-channel, so a sender flagged overall may vanish per channel.)
- How would you express the threshold without HAVING? (Wrap the aggregation in a CTE and filter on `msg_count` > 2 in an outer WHERE. Same plan in most engines, but it shows HAVING is just a post-group WHERE.)