Monthly Signup Counts
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
- General Assembly
Problem
Produce each month's signup count from the users table in two forms, the whole number the dashboard shows and a floating-point copy, listed from the oldest month forward. An analyst feeds that copy into a forecasting model that only accepts floating-point inputs, so an integer in that column will not load.
Signups, month by month.
Tables
users
Columns: user_id INTEGER, username TEXT, email TEXT, signup_date TEXT, account_status TEXT, age_bucket TEXT
| user_id | username | signup_date | account_status | age_bucket | |
|---|---|---|---|---|---|
| 100 | alice | alice@example.com | 2025-02-02 | inactive | 25-34 |
| 197 | aaron42 | aaron42@example.com | 2026-03-03 | suspended | 35-44 |
| 294 | amelia | amelia@example.com | 2024-04-04 | pending_verification | 45-54 |
| 391 | arjun | arjun@example.com | 2025-05-05 | active | 55-64 |
| 488 | ava99 | ava99@example.com | 2026-06-06 | inactive | 65+ |
| 585 | andrew_k | andrew_k@example.com | 2024-07-07 | suspended | NULL |
| 682 | anika | anika@example.com | 2025-08-08 16:34:00 | pending_verification | 18-24 |
| 779 | aiden | aiden@example.com | 2026-09-09 14:53:00 | active | 25-34 |
| 876 | aria_b | aria_b@example.com | 2024-10-10 | inactive | 35-44 |
| 973 | brian | brian@example.com | 2025-11-11 | suspended | 45-54 |
Expected output
| month | signup_count | signup_count_float |
|---|---|---|
| 2023-01 | 8 | 8 |
| 2023-05 | 9 | 9 |
| 2023-09 | 8 | 8 |
| 2024-01 | 8 | 8 |
| 2024-04 | 17 | 17 |
Worked solution and explanation
This is a monthly bucket count with one twist, and the whole grade rides on it: the same number has to leave the query twice, in two different types. Anyone can turn signup_date into a month and count rows. What separates candidates is noticing that COUNT(*) is an integer however you alias it, so selecting it a second time ships 8 where the model was promised 8.0. Skip the explicit conversion and the result looks right in a terminal, then fails the moment a type-strict consumer reads it.
Building it
Step 1: Bucket each signup by its month
Format signup_date as a year-month label with STRFTIME('%Y-%m', signup_date). Two rows in users carry a time of day ('2025-08-08 16:34:00'). The formatting throws the time away, so those signups fall into their month like every other row.
Step 2: Count once, emit twice
Group on that month expression and take COUNT(*). The dashboard column is the count as is. The model column is the same aggregate passed through CAST(COUNT(*) AS DOUBLE). The engine computes the aggregate once per group; the cast only changes how the value is typed.
Step 3: Walk the calendar forward
Sort by month. Because the label is zero-padded, text order and calendar order agree, so '2023-09' sorts before '2023-10' without parsing anything back into a date.
SELECT STRFTIME('%Y-%m', signup_date) AS month,
COUNT(*) AS signup_count,
CAST(COUNT(*) AS DOUBLE) AS signup_count_float
FROM users
GROUP BY STRFTIME('%Y-%m', signup_date)
ORDER BY month- Count selected twice
COUNT(*) AS signup_count_floatreturns 8, 9, 17. The column name promises a float and the value is still an integer, so the model rejects it.- Count cast to a double
CAST(COUNT(*) AS DOUBLE)returns 8.0, 9.0, 17.0. Same value, declared type, and the integer column next to it is untouched.
Common follow-up questions
- The model now wants months with no signups to appear as 0.0. What changes? (Tests building a month spine and keeping empty months with an outer join.)
- If `signup_date` were stored in UTC and the business counts months in local time, where does the conversion go? (Tests converting the time zone before bucketing, not after.)
- What if the model needs each month's share of all signups instead of the raw count? (Tests why the decimal type matters once division enters the query.)