Did Anyone Actually Read It?

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
Asked in
Meta

Problem

Growth is reviewing which platforms actually drove push engagement in 2026, setting aside anything sent in earlier years. A notification counts as a real win only when it reached the device (status = 'delivered', though the data is careless about capitalization) and the user opened it (opened = 1). For each platform, return the count of these wins as opened_count, from the most to the fewest.

A push isn't a win until a thumb taps it.

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 4 of 4 rows
platformopened_count
ios5
basic4
android3
web3

Worked solution and explanation

What this is really testing

This is a multi-predicate count sliced by a dimension, wearing an engagement-dashboard costume. Anyone can write COUNT(*) with a WHERE clause. The real skill being probed: can you spot that the text columns are dirty and normalize them before you filter and before you group? The status field arrives as 'delivered', 'Delivered', and 'DELIVERED', and the platform field arrives as 'android', 'Android', and 'ANDROID'. Compare status = 'delivered' directly and you silently drop every capitalized win. Group by the raw platform and one real platform explodes into three phantom groups that will never match the expected three-row answer.


Break it down

Step 1: Scope to the target year

WHERE strftime('%Y', sent_at) = '2026' scopes to notifications sent during the target year by pulling the year out of the timestamp. The 2025 row in the sample is a deliberate trap: it is delivered and opened, but it falls outside the window and must not be counted.

Step 2: Normalize status, require the open

AND LOWER(status) = 'delivered' folds every capitalization of the status into one match, and AND opened = 1 keeps only the notifications a user actually tapped. Because opened = 1 is a strict equality, NULL and 0 both fall away for free, so failed, bounced, and unopened rows never survive.

Step 3: Group on the normalized platform and count

GROUP BY LOWER(platform) collapses the case variants of each platform into a single bucket before counting, and COUNT(*) AS opened_count tallies the survivors per bucket. ORDER BY opened_count DESC, platform ASC puts the busiest platform on top and settles any ties deterministically by name.


The solution

Normalize, filter, group, count
SELECT LOWER(platform) AS platform,
       COUNT(*) AS opened_count
FROM push_notifs
WHERE opened = 1
  AND LOWER(status) = 'delivered'
  AND strftime('%Y', sent_at) = '2026'
GROUP BY LOWER(platform)
ORDER BY opened_count DESC, platform ASC

Common follow-up questions