Malek Damiri

Find streaks with gaps and islands

·

"How many months in a row has this customer paid?" sounds like it needs a loop. It doesn't. The gaps-and-islands trick finds every unbroken run in one query.

The data

create table payments as select * from (values
  ('acme',   date '2026-01-01'), ('acme', date '2026-02-01'), ('acme', date '2026-03-01'),
  ('acme',   date '2026-05-01'), ('acme', date '2026-06-01'),
  ('globex', date '2026-02-01'), ('globex', date '2026-04-01')
) t(account, paid_month);

The query

with numbered as (
  select account, paid_month,
         date_diff('month', date '2000-01-01', paid_month) as month_number,
         row_number() over (partition by account order by paid_month) as rn
  from payments
)
select account,
       min(paid_month) as streak_start,
       max(paid_month) as streak_end,
       count(*) as months
from numbered
group by account, month_number - rn
order by account, streak_start;
account   streak_start   streak_end   months
acme      2026-01-01     2026-03-01        3
acme      2026-05-01     2026-06-01        2
globex    2026-02-01     2026-02-01        1
globex    2026-04-01     2026-04-01        1

Why it works

Within a streak, the month number and the row number both go up by one each row, so their difference stays the same. A skipped month bumps the month number by two while the row number still goes up by one, so the difference changes and a new group starts. Acme's January to March share one value; May and June share another.

Uses

Longest paying streak, customers who churned and came back, consecutive days active, and how long an account has been above a usage threshold. In Snowflake, swap in datediff('month', '2000-01-01', paid_month). The anchor date doesn't matter, as long as it's the same for every row.

One assumption to check first: one row per account per month. If the table can hold two payments in the same month, deduplicate before numbering, or the streaks come out too long.

Every query here ran in DuckDB 1.5.1 and returned the output shown. This note was written with AI assistance.