Gaps, islands & sessionization
Concept  ·  SQL & Querying

Subtracting a per-group row_number from a global one gives a constant per consecutive run — derive it, don't recite it.

Consecutive-run (island) detection keys on t minus a ROW_NUMBER ordered by t: within a run both climb together so the difference is constant, and a gap shifts it. Grouping by that difference separates runs that grouping by the value alone would merge.

See it break
reads
tgate
1in
2in
3out
4in
The trap
SELECT
  COUNT(*) AS len
FROM
  reads
    WHERE
      gate = 'in'
  ;
The fix
SELECT
  island,
  COUNT(*) AS len
FROM
  (
    SELECT
      t - ROW_NUMBER() OVER (
        ORDER BY
          t
      ) AS island
    FROM
      reads
    WHERE
      gate = 'in'
  )
GROUP BY
  island
ORDER BY
  island;
The trap
3
The fix
02
11

Counting 'in' rows says one run of 3, but the gate reopened at t=4 after an 'out' — the row_number-difference key splits it into the two real runs.

The rule

Compute island = t - ROW_NUMBER() OVER (ORDER BY t) over the matching rows, then GROUP BY that key to get one group per consecutive run.

day - ROW_NUMBER() OVER (PARTITION BY usr ORDER BY day)
Shows up elsewhere
LAG detects the gap
Try one →Prove it in practice →