Gap questions are self-join questions in disguise, and LAG is the self-join done for you: same partition, previous row by the ORDER BY. Two precision points carry the marks: the gap belongs to the row after the silence (the later match carries the long gap, not the earlier one) — off-by-one flaggings come from forgetting this; and the leading NULL means 'no predecessor', which most alerts should skip, not zero-fill.
SELECT player, d FROM ( SELECT player, d, LEAD (d) OVER ( PARTITION BY player ORDER BY d ) - d AS gap FROM matches ) WHERE gap > 21 ORDER BY d;
SELECT player, d FROM ( SELECT player, d, d - LAG (d) OVER ( PARTITION BY player ORDER BY d ) AS gap FROM matches ) WHERE gap > 21 ORDER BY d;
The absence alert flagged the match before the long gap instead of the one after it — LAG measures each row against its predecessor, and the first row's LAG is NULL by design.
Use d - LAG(d) OVER (PARTITION BY ... ORDER BY ...) and state the leading-NULL policy explicitly.