The Spending Rhythm

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

A monthly spending cadence report needs each user's transaction count broken out by calendar month so the finance team can spot irregular purchasing patterns. Lay it out as a per-user timeline, working through users from the lowest id upward and running each user's months from earliest to latest.

Every month tells a spending story, user by user.

Tables

transactions

Columns: transaction_id INTEGER, user_id INTEGER, product_id INTEGER, quantity INTEGER, total_amount REAL, transaction_date TEXT

transactions: 10 of 200 rows
transaction_iduser_idproduct_idquantitytotal_amounttransaction_date
10671971001223.462026-02-02
11342941050336.932026-03-03
12013911099450.42026-04-04
12684881148563.872026-05-05
13355851197002026-06-06
14026821246290.812026-07-07
146977912953104.282026-08-08
153687613444117.752026-09-09
160397313935-452026-01-10
1670107014421144.692026-02-11

Expected output

First 5 of 102 rows
user_idmonthtransaction_count
1002026-012
1002026-032
1972022-101
1972026-023
2942023-111

Worked solution and explanation

What this problem really is

This is a per-user, per-month frequency count wearing a finance-report costume. Anyone can reach for COUNT(*) and group by user_id. What actually separates candidates is recognizing that the second grouping key is a DERIVED value, the year-month pulled out of transaction_date, not the raw date itself. Group on the raw date and you get one row per day per user instead of one per month. The row counts collapse to mostly 1s, and the cadence report the finance team asked for dissolves into daily noise.


Break down the requirements

Step 1: Bucket by user and month

The output grain is one row per user per calendar month, so both user_id and the month string belong in the GROUP BY. Format the month as STRFTIME('%Y-%m', transaction_date) so it sorts chronologically and reads cleanly, then COUNT(*) the rows that fall into each bucket.

Step 2: Order the final output

The report is ordered by user, then month. Because the month is the zero-padded %Y-%m string, an ordinary ORDER BY user_id, month sorts it in true calendar order: for that format, lexicographic and chronological order coincide. Order on the raw date or an unpadded month and the sequence breaks.


The solution

User-month grain for spending cadence
SELECT user_id,
    STRFTIME('%Y-%m', transaction_date) AS month,
    COUNT(*) AS transaction_count
FROM transactions
GROUP BY user_id, STRFTIME('%Y-%m', transaction_date)
ORDER BY user_id, month

Common follow-up questions