When They Opened

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 marketing team wants a month-by-month read on push engagement across the year. For each month, find how many notifications were opened, busiest month first.

Push after push goes out. Some months, people actually read them.

Tables

push_notifs

Columns: notif_id INTEGER, user_id INTEGER, title TEXT, platform TEXT, status TEXT, sent_at TEXT, opened INTEGER, campaign TEXT

push_notifs: 10 of 200 rows
notif_iduser_idtitleplatformstatussent_atopenedcampaign
1053100New message from teamandroidpending2026-02-02 01:07:000winback_feb
1106197Weekly digest readywebfailed2026-03-03 02:14:000promo_summer
1159294Price drop alertiOSbounced2026-04-04 03:21:001onboard_v2
1212391Security alertAndroidDelivered2026-05-05 04:28:000NULL
1265488Payment receivedWEBFAILED2026-06-06 05:35:00NULLloyalty_2024
1318585Complete your profilebasicdelivered2026-07-07 06:42:001NULL
1371682New feature availablepremiumpending2026-08-08 07:49:000reengagement
1424779Subscription renewaliosfailed2026-09-09 08:56:000NULL
1477876Flash sale: 50% offandroidbounced2026-01-10 09:03:001flash_sale
1530973Your order shipped!webDelivered2026-02-11 10:10:00NULLretention_q1

Expected output

First 5 of 9 rows
monthopened_count
19
49
37
66
51

Worked solution and explanation

What this really is

Strip the marketing costume off and this is a per-bucket tally of a boolean flag, where the bucket is a month you have to manufacture out of a timestamp. Anyone can write COUNT(*). The two things that actually separate candidates: pulling the month out of sent_at in a type that groups cleanly, and counting only the confirmed opens without letting a NULL flag sneak in. Miss either and the number is quietly wrong, which is the worst kind of wrong in a dashboard.

The trap

Building it

Step 1: Manufacture the month bucket

Derive the month with CAST(strftime('%m', sent_at) AS INTEGER). Doing the cast here means every later reference (the SELECT, the GROUP BY, the ORDER BY) sees a clean 1 through 12.

Step 2: Keep only the opens

WHERE opened = 1 restricts to confirmed opens before any counting happens, excluding both 0 and NULL flags.

Step 3: Tally and order

GROUP BY month collapses each month into one row, COUNT(*) tallies it, and ORDER BY opened_count DESC, month puts the busiest month on top with a deterministic tie-break so equal counts don't shuffle between runs.

Per-month opened counts
SELECT CAST(strftime('%m', sent_at) AS INTEGER) AS month,
       COUNT(*) AS opened_count
FROM push_notifs
WHERE opened = 1
GROUP BY month
ORDER BY opened_count DESC, month
Filter then COUNT(*)
WHERE opened = 1 then COUNT(*): the intent is explicit, NULL and 0 are both excluded by the predicate, and the count is over rows you have already vouched for.
SUM(opened)
SUM(opened) over the whole table: leans on the flag being exactly 1 or 0, treats NULL as it happens to fall out, and mixes the filter into the aggregate where a reviewer can't see it. Same answer here, weaker habit.

Common follow-up questions