Advanced SQL & Data Engineering • Training 09
Article-Training • SQL Server–Friendly Series

Gaps and Islands

Find Streaks, Runs and Missing Periods

Gaps and Islands problems turn messy sequences into meaningful runs and breaks.

Ordered Sequence → Detect Break → Create Group → Aggregate Island → Measure Gap
1 2 3
|
6 7 8
|
12
Island
→
Gap
→
Island
8learning modules
24interactive practices
5rapid review questions
50%certificate threshold
Learning target
Use window functions to turn ordered rows into islands and gaps.
Practice progress0 / 24

Complete 12 of 24 practices (50%) and enter your name to unlock the Certificate of Participation.

MODULE 01
🧠

What Gaps and Islands Really Means

An island is a consecutive run. A gap is the break between runs. The SQL challenge is to convert row-by-row continuity into a group identifier that can be aggregated.

🏝️
The business defines what 'consecutive' means

For daily attendance, consecutive may mean adjacent calendar dates. For machine telemetry, it may mean status values with no change. For invoices, it may mean sequential document numbers.

Core ideas

  • Island = uninterrupted run
  • Gap = break between runs
  • Ordering is essential
  • The continuity rule must be explicit
Simple number sequence
Values:
1, 2, 3, 6, 7, 8, 12

Islands:
1-3
6-8
12

Gaps:
4-5
9-11
✅

Gaps and islands is not one SQL trick; it is a family of patterns built around detecting where continuity breaks.

Practice — 3 cases

Practice 1 / Práctica 1
What is an island in a gaps-and-islands problem?
Practice 2 / Práctica 2
What is a gap?
Practice 3 / Práctica 3
What must be defined before solving gaps and islands?
MODULE 02
👣

Detect Breaks with LAG

LAG is one of the clearest ways to detect continuity breaks. It lets each row see the previous value so you can ask whether the current row continues the run or starts a new one.

🏝️
Every island begins where the previous row no longer connects

If yesterday is the previous date, today's row continues the island. If the previous date was three days ago, today's row begins a new island.

Core ideas

  • Use LAG with the correct PARTITION BY and ORDER BY
  • Create a break flag: 1 = new island, 0 = continuation
  • The first row normally begins an island
  • Break logic must match business time/sequence rules
Break flag with LAG
WITH x AS
(
    SELECT EmployeeID,
           WorkDate,
           LAG(WorkDate) OVER
           (
               PARTITION BY EmployeeID
               ORDER BY WorkDate
           ) AS PrevDate
    FROM dbo.Attendance
)
SELECT *,
       CASE
           WHEN PrevDate IS NULL THEN 1
           WHEN DATEDIFF(day, PrevDate, WorkDate) > 1 THEN 1
           ELSE 0
       END AS NewIslandFlag
FROM x;
✅

LAG converts continuity from an abstract idea into a row-by-row comparison you can test.

Practice — 3 cases

Practice 4 / Práctica 4
What does LAG help you compare?
Practice 5 / Práctica 5
For daily data, which condition can detect a break?
Practice 6 / Práctica 6
What should the first row usually do in a break flag?
MODULE 03
🧮

Turn Break Flags into Island IDs

Once each new island is marked with 1, a running SUM can assign a stable group number. Every time a break appears, the group number increments.

🏝️
Break flags become group boundaries

Rows 1, 2 and 3 may all have GroupID = 1. After a gap, rows 6, 7 and 8 receive GroupID = 2.

Core ideas

  • Mark island starts first
  • Run SUM over the same partition and order
  • Every increment creates a new island ID
  • After grouping, normal aggregation becomes easy
Running island group
WITH Breaks AS
(
    SELECT EmployeeID,
           WorkDate,
           CASE
             WHEN LAG(WorkDate) OVER
                  (PARTITION BY EmployeeID ORDER BY WorkDate) IS NULL
               OR DATEDIFF
                  (
                    day,
                    LAG(WorkDate) OVER
                    (PARTITION BY EmployeeID ORDER BY WorkDate),
                    WorkDate
                  ) > 1
             THEN 1 ELSE 0
           END AS NewIslandFlag
    FROM dbo.Attendance
),
Grouped AS
(
    SELECT *,
           SUM(NewIslandFlag) OVER
           (
               PARTITION BY EmployeeID
               ORDER BY WorkDate
               ROWS UNBOUNDED PRECEDING
           ) AS IslandID
    FROM Breaks
)
SELECT *
FROM Grouped;
✅

The running-SUM pattern is one of the most reusable gaps-and-islands techniques in SQL Server.

Practice — 3 cases

Practice 7 / Práctica 7
How can you turn break flags into island group numbers?
Practice 8 / Práctica 8
Why does running SUM work?
Practice 9 / Práctica 9
What should the running window order match?
MODULE 04
📏

Summarize Each Island

Once rows share an IslandID, the problem becomes ordinary aggregation. You can calculate start, end, duration, count and other business measures for each run.

🏝️
Window logic finds the run; GROUP BY summarizes it

A machine may have an 'Online' island from 08:00 to 11:42 and another from 12:03 to 18:10. Those periods can now be treated as business objects.

Core ideas

  • MIN gives island start
  • MAX gives island end
  • COUNT gives rows/events in the island
  • Duration should match the business grain
Aggregate islands
SELECT EmployeeID,
       IslandID,
       MIN(WorkDate) AS StartDate,
       MAX(WorkDate) AS EndDate,
       COUNT(*) AS DaysInRun
FROM Grouped
GROUP BY EmployeeID, IslandID
ORDER BY EmployeeID, StartDate;
✅

After grouping, an island becomes a compact interval the business can report, rank and compare.

Practice — 3 cases

Practice 10 / Práctica 10
After rows have an IslandID, how do you get the island start and end?
Practice 11 / Práctica 11
How can you measure island length in days for consecutive daily dates?
Practice 12 / Práctica 12
What can island aggregation tell you?
MODULE 05
🕳️

Measure the Gaps Between Islands

After islands are summarized, LEAD can expose the next island's start. That makes it easy to compute the missing interval between current EndDate and next StartDate.

🏝️
Once islands exist, gaps become intervals too

If an attendance streak ends on May 10 and the next begins May 14, the gap is May 11 through May 13.

Core ideas

  • Use LEAD(StartDate) across ordered islands
  • GapStart is usually the day/value after current End
  • GapEnd is usually the day/value before next Start
  • Only return gaps that actually exist
Gap boundaries
WITH IslandSummary AS
(
    SELECT EmployeeID,
           IslandID,
           MIN(WorkDate) AS StartDate,
           MAX(WorkDate) AS EndDate
    FROM Grouped
    GROUP BY EmployeeID, IslandID
),
x AS
(
    SELECT *,
           LEAD(StartDate) OVER
           (
               PARTITION BY EmployeeID
               ORDER BY StartDate
           ) AS NextStartDate
    FROM IslandSummary
)
SELECT EmployeeID,
       DATEADD(day, 1, EndDate) AS GapStart,
       DATEADD(day, -1, NextStartDate) AS GapEnd
FROM x
WHERE NextStartDate > DATEADD(day, 1, EndDate);
✅

Gaps are easiest to calculate after islands have already been reduced to start/end intervals.

Practice — 3 cases

Practice 13 / Práctica 13
How can LEAD help identify gaps between islands?
Practice 14 / Práctica 14
For daily islands ending Jan 3 and starting Jan 6, what dates are missing?
Practice 15 / Práctica 15
What should gap boundaries usually exclude?
MODULE 06
🔄

Status Islands: Consecutive Runs of the Same State

Not every island is defined by numeric adjacency. In telemetry, workflow and operations data, an island can mean a continuous run of the same status until the value changes.

🏝️
A state change is the gap boundary

A machine may be Online for six events, Offline for three, then Online again. The two Online runs are separate islands even though the status text is the same.

Core ideas

  • Compare current status to LAG(Status)
  • A change flag begins a new status island
  • Running SUM creates the status-run ID
  • Aggregate by entity + run ID + status
Status run pattern
WITH x AS
(
    SELECT DeviceID,
           EventTime,
           Status,
           LAG(Status) OVER
           (
               PARTITION BY DeviceID
               ORDER BY EventTime
           ) AS PrevStatus
    FROM dbo.DeviceStatus
),
Breaks AS
(
    SELECT *,
           CASE
             WHEN PrevStatus IS NULL OR PrevStatus <> Status
             THEN 1 ELSE 0
           END AS NewRunFlag
    FROM x
)
SELECT *,
       SUM(NewRunFlag) OVER
       (
           PARTITION BY DeviceID
           ORDER BY EventTime
           ROWS UNBOUNDED PRECEDING
       ) AS RunID
FROM Breaks;
✅

The same break-flag and running-SUM idea works for state changes as well as missing dates.

Practice — 3 cases

Practice 16 / Práctica 16
What changes in a status-island problem?
Practice 17 / Práctica 17
Which comparison helps detect a status change?
Practice 18 / Práctica 18
What can status islands represent?
MODULE 07
🧠

Alternative Pattern: ROW_NUMBER Offset

For fixed-step sequences, an elegant gaps-and-islands trick is to subtract ROW_NUMBER from the value. Within a consecutive run, both the value and row number increase together, so their difference stays constant.

🏝️
Consecutive values create a constant offset

For 10,11,12 and row numbers 1,2,3, the difference is always 9. After a gap, the difference changes and a new island appears.

Core ideas

  • Works beautifully for fixed-step sequences
  • For dates, subtract row number in days
  • The derived constant becomes the island key
  • Use LAG/running SUM when continuity rules are more complex
ROW_NUMBER offset
WITH x AS
(
    SELECT EmployeeID,
           WorkDate,
           ROW_NUMBER() OVER
           (
               PARTITION BY EmployeeID
               ORDER BY WorkDate
           ) AS rn
    FROM dbo.Attendance
),
g AS
(
    SELECT *,
           DATEADD(day, -rn, WorkDate) AS IslandKey
    FROM x
)
SELECT EmployeeID,
       MIN(WorkDate) AS StartDate,
       MAX(WorkDate) AS EndDate,
       COUNT(*) AS DaysInRun
FROM g
GROUP BY EmployeeID, IslandKey;
✅

Use the offset trick when the sequence step is fixed; use break flags when the business rule is more flexible.

Practice — 3 cases

Practice 19 / Práctica 19
What is the row-number offset technique often used for?
Practice 20 / Práctica 20
For integer sequence 10,11,12 with ROW_NUMBER 1,2,3, what stays constant?
Practice 21 / Práctica 21
When is the row-number offset technique especially elegant?
MODULE 08
🏭

Production Design: Choose the Right Island Pattern

There is no single universal gaps-and-islands query. LAG + running SUM is flexible. ROW_NUMBER offset is elegant for fixed-step sequences. Calendar tables help when you need explicit missing dates. The right solution starts with the business definition of continuity.

🏝️
Choose the pattern after you define the break rule

If 'consecutive' means adjacent business days, a calendar table may be required. If it means identical statuses until change, a status comparison is enough.

Core ideas

  • Define entity partition and ordering key
  • Define exactly what starts a new island
  • Use deterministic ordering for ties
  • Validate with representative edge cases and execution plans
Production checklist
1. What entity owns the sequence?
2. What column defines order?
3. What exactly counts as consecutive?
4. What starts a new island?
5. Are duplicate timestamps/values possible?
6. Do you need islands, gaps, or both?
7. Is LAG + running SUM clearer?
8. Is ROW_NUMBER offset valid for a fixed step?
9. Are calendar/business-day rules involved?
10. Are indexes aligned with entity + sequence order?
11. Have edge cases been tested?
12. Are logical reads / sorts acceptable at scale?
✅

Gaps and islands is a modeling problem first and a window-function problem second.

Practice — 3 cases

Practice 22 / Práctica 22
What index pattern often helps gaps-and-islands queries?
Practice 23 / Práctica 23
What is a strong production validation for an island query?
Practice 24 / Práctica 24
What is the strongest production rule for gaps and islands?
5-Question Knowledge Check

Can you turn a sequence into islands and gaps?

Open each item after answering it in your own words. The 24 interactive practices above drive certificate progress.

1. What defines an island?

A consecutive run of rows connected by the continuity rule.

2. How does LAG help detect new islands?

It exposes the previous ordered value so the current row can be tested for continuity or a break.

3. How do break flags become island IDs?

A running SUM turns break flags into island group numbers.

4. When is ROW_NUMBER offset useful?

For fixed-step sequences where the derived offset remains constant within an island.

5. What is the most important design decision?

Define exactly what consecutive means before choosing the SQL pattern.

Gaps & Islands Pattern Map

Choose the pattern that matches the continuity rule

Problem shapeStrong pattern
Irregular break ruleLAG + Break Flag + Running SUM
Fixed-step numbers/datesROW_NUMBER Offset
Same status until changeLAG(Status) + Running SUM
Need exact missing datesCalendar / tally table comparison
Need summarized runsGROUP BY IslandID + MIN/MAX/COUNT

Certificate of Participation

Complete at least 12 of the 24 practice cases (50%) and enter your name.

0 / 24 • 0%
Production note

Gaps and islands is a sequence-modeling problem powered by window functions.