IntermediateCTEs & Recursion25 points

Cloudpeak MRR, month by month

The CFO wants the classic SaaS chart: monthly recurring revenue (MRR) at the end of each month of the first half of 2026. A subscription row counts for a month when it was active on that month's last day: it started on or before that day, and its end_date is NULL or later than that day.

Dataset: Cloudpeak. Every plan change writes a new subscriptions row: the old row gets an end_date and the new row starts on that same date. A row with end_date NULL is the current plan. A customer who cancels has a last row whose end_date has no follow-up row.

Task: build the list of months from 2026-01 to 2026-06 with a recursive CTE (do not type the months by hand in a VALUES list). Return month (format YYYY-MM), active_accounts (number of active subscription rows) and mrr_usd (sum of mrr). Sort by month.

The data you can query

Each table is shown with its column types and the first rows, so you can see exactly what the data looks like.

accounts40 rows
id INTEGERcompany TEXTcountry TEXTindustry TEXTsignup_date TEXT
1Aurora FreightGermanyLogistics2025-09-22
2Bluegrid EnergyUnited KingdomEnergy2025-10-13
3Cedar Health LabsUnited StatesHealthcare2026-03-07
4Delta Pay SystemsNigeriaFintech2025-12-26
5Evergreen Retail GroupCanadaRetail2025-02-12
6Falcon AerospaceUnited Arab EmiratesAerospace2025-02-12

Showing the first 6 of 40 rows.

events149 rows
id INTEGERaccount_id INTEGERuser_id INTEGERfeature TEXTevent_ts TEXT
1112api2025-09-24 16:05:34
2113api2026-04-29 12:15:40
3221api2025-10-15 15:14:48
4224sso2025-10-14 13:06:32
5224alerts2025-10-16 10:57:11
6333alerts2026-06-01 10:37:06

Showing the first 6 of 149 rows.

invoices168 rows
id INTEGERaccount_id INTEGERissued_on TEXTdue_on TEXTpaid_on TEXTamount_usd REAL
112026-01-012026-01-312026-01-22499
212026-02-012026-03-032026-02-13499
312026-03-012026-03-312026-04-23499
412026-04-012026-05-012026-04-23499
512026-05-012026-05-312026-05-20499
612026-06-012026-07-01NULL499

Showing the first 6 of 168 rows.

subscriptions65 rows
id INTEGERaccount_id INTEGERplan TEXTmrr REALstart_date TEXTend_date TEXT
11Scale4992025-09-22NULL
22Starter492025-10-13NULL
33Growth1992026-03-07NULL
44Growth1992025-12-26NULL
55Starter492025-02-122026-02-01
65Growth1992026-02-012026-05-01

Showing the first 6 of 65 rows.

tickets40 rows
id INTEGERaccount_id INTEGERopened_ts TEXTresolved_ts TEXTpriority TEXT
122025-11-28 08:44:00NULLNormal
2372026-01-29 15:51:002026-01-31 09:55:00Low
3182025-05-01 13:33:002025-05-04 09:13:00Low
4112025-09-24 16:09:002025-09-24 19:40:00Normal
5392025-12-06 15:34:002025-12-07 14:17:00High
6152026-03-20 11:06:002026-03-23 20:44:00Low

Showing the first 6 of 40 rows.

Your query runs on SQLite. Write a single SELECT; use LIMIT instead of TOP.

Need a hint?

date(m, '+1 month', '-1 day') gives the last day of the month that starts on m. Join each month to the subscriptions active on that day.