No Gaps

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

Each row of users is one account with a username, an email, an age bucket and an account status, and the email or age bucket is sometimes missing. A campaign blast needs every active account as a mail-merge contact with no blank fields, so a missing email reads 'unknown' and a missing age bucket reads 'unspecified'.

Zero blanks. A clean contact list.

Tables

users

Columns: user_id INTEGER, username TEXT, email TEXT, signup_date TEXT, account_status TEXT, age_bucket TEXT

users: 10 of 200 rows
user_idusernameemailsignup_dateaccount_statusage_bucket
100alicealice@example.com2025-02-02inactive25-34
197aaron42aaron42@example.com2026-03-03suspended35-44
294 ameliaamelia@example.com2024-04-04pending_verification45-54
391arjun arjun@example.com2025-05-05active55-64
488ava99ava99@example.com2026-06-06inactive65+
585andrew_kandrew_k@example.com2024-07-07suspendedNULL
682anikaanika@example.com2025-08-08 16:34:00pending_verification18-24
779aidenaiden@example.com2026-09-09 14:53:00active25-34
876aria_baria_b@example.com2024-10-10inactive35-44
973brianbrian@example.com2025-11-11suspended45-54

Expected output

First 5 of 50 rows
usernameemailage_bucket
arjun arjun@example.com55-64
aidenaiden@example.com25-34
beatricebeatrice@example.com65+
brooke7brooke7@example.com35-44
cyruscyrus@example.comunspecified

Worked solution and explanation

This is a fill-not-filter question dressed up as a campaign export. Ten of the 50 active accounts on this seed are missing an email or an age bucket, and 'no blank fields' tempts half the room into WHERE email IS NOT NULL AND age_bucket IS NOT NULL: a spotless list of 40 people and a quietly smaller campaign. The other half projects the raw columns and ships blanks to the merge. The skill is seeing that a blank is a cell problem, not a row problem: the row stays, the cell gets a default.

Walking to the answer

Step 1: Scope to the exact 'active' status

account_status holds 'active', 'inactive', 'suspended' and 'pending_verification'. Only the first is a recipient, so the filter is an equality on 'active'. That WHERE is the only clause allowed to remove rows; nothing about missing data should touch the row count.

Step 2: Fill each blank cell with its own placeholder

COALESCE(email, 'unknown') returns the email when it exists and 'unknown' when it is NULL; COALESCE(age_bucket, 'unspecified') does the same for the bucket. One COALESCE per column, because each column has its own placeholder and the blanks are independent: on this seed no active account is missing both.

Step 3: Alias the filled columns back to email and age_bucket

The merge template binds by column name. A result column that comes out named after the expression breaks the template without a single wrong value in it, so name the filled columns exactly what the raw ones were called.

Fill the blanks, keep the rows
SELECT
    username,
    COALESCE(email, 'unknown') AS email,
    COALESCE(age_bucket, 'unspecified') AS age_bucket
FROM users
WHERE account_status = 'active'
The clean-looking list that loses ten recipients
SELECT
    username,
    email,
    age_bucket
FROM users
WHERE account_status = 'active'
  AND email IS NOT NULL
  AND age_bucket IS NOT NULL

Every cell is filled, and 10 of the 50 active accounts are gone.

Filter the blanks out
email IS NOT NULL AND age_bucket IS NOT NULL makes the output look clean by shrinking it: 40 rows, and nobody downstream can tell ten recipients were dropped.
Fill the blanks in
COALESCE per column keeps all 50 rows and makes every missing value visible as a known placeholder the sender can act on.

Common follow-up questions