Field note · February 21, 2026
Finding the gaps in saas-subscriptions
gaps and islands against a real schema, and the clause in it that is doing the work.
Finding the gaps in saas-subscriptions comes up often enough to be worth a note. saas-subscriptions is a convenient thing to try it on — 2,500 rows, one row per account.
with numbered as (
select plan, signed_up_on,
row_number() over (partition by plan order by signed_up_on) as rn
from saas_subscriptions
)
select plan,
min(signed_up_on) as island_start,
max(signed_up_on) as island_end,
count(*) as readings
from (
select *, date_trunc('day', signed_up_on) - (rn * interval '1 day') as grp_key
from numbered
) t
group by plan, grp_key
order by plan, island_start;The trick is that subtracting a dense row number from a dense date gives a constant inside a run and a different constant across a gap. Once you have seen it the query is obvious; before that it looks like a magic trick.
Run it against the real file in the SQL playground — the engine there is enough of a SQL implementation to execute this as written, and the dataset is already loaded. The pattern page has the version with the failure modes spelled out.
Window functions are the difference between SQL that describes rows and SQL that describes sequences. Almost every "we exported it to pandas to do this bit" turns out to be one of these five.