Web Analytics Made Easy - Statcounter

Mastering the “Gaps and Islands” Problem in SQL Server: Finding Consecutive Day Streaks

Introduction

Mastering the Gaps and Islands Problem in SQL Server: Finding Consecutive Day Streaks

Have you ever needed to answer questions like these?

  • How many consecutive days did a customer place an order?
  • How many days did an employee log into the system without interruption?
  • What was the longest attendance streak?
  • How many consecutive days was a machine operational?
  • How many days did a user maintain a GitHub contribution streak?

These problems are commonly known as Gaps and Islands problems.

Although they may seem complicated at first, SQL Server provides elegant solutions using Window Functions, making them both efficient and easy to understand.

In this article, you’ll learn one of the most popular techniques for solving consecutive-day problems.

What are Gaps and Islands?

A sequence of consecutive values is called an Island.

Missing values between consecutive records are called Gaps.

For example:

DateStatus
01-JanLogin
02-JanLogin
03-JanLogin
05-JanLogin
06-JanLogin
09-JanLogin

Here,

Island 1

  • 01-Jan
  • 02-Jan
  • 03-Jan

Gap

  • 04-Jan

Island 2

  • 05-Jan
  • 06-Jan

Gap

  • 07-Jan
  • 08-Jan

Island 3

  • 09-Jan

Sample Data

CREATE TABLE EmployeeLogin
(
    EmployeeID INT,
    LoginDate DATE
);

INSERT INTO EmployeeLogin
VALUES
(101,'2025-01-01'),
(101,'2025-01-02'),
(101,'2025-01-03'),
(101,'2025-01-05'),
(101,'2025-01-06'),
(101,'2025-01-09');

Current data

EmployeeIDLoginDate
1012025-01-01
1012025-01-02
1012025-01-03
1012025-01-05
1012025-01-06
1012025-01-09

Step 1 – Generate Row Numbers

SELECT
    EmployeeID,
    LoginDate,
    ROW_NUMBER() OVER
    (
        PARTITION BY EmployeeID
        ORDER BY LoginDate
    ) AS RN
FROM EmployeeLogin;

Output

LoginDateRN
01-Jan1
02-Jan2
03-Jan3
05-Jan4
06-Jan5
09-Jan6

The row number simply assigns a sequential number to each login date.

Step 2 – Create a Group Identifier

Now comes the trick.

Subtract the row number from the date.

SELECT
    EmployeeID,
    LoginDate,
    DATEADD(day,
            -ROW_NUMBER() OVER
            (
                PARTITION BY EmployeeID
                ORDER BY LoginDate
            ),
            LoginDate) AS GroupID
FROM EmployeeLogin;

Output

LoginDateGroupID
01-Jan2024-12-31
02-Jan2024-12-31
03-Jan2024-12-31
05-Jan2025-01-01
06-Jan2025-01-01
09-Jan2025-01-03

Notice something interesting?

All consecutive dates produce the same GroupID.

That becomes our Island identifier.

Step 3 – Aggregate Each Island

Now simply group by the generated GroupID.

WITH LoginGroups AS
(
    SELECT
        EmployeeID,
        LoginDate,
        DATEADD(day,
            -ROW_NUMBER() OVER
            (
                PARTITION BY EmployeeID
                ORDER BY LoginDate
            ),
            LoginDate) AS GroupID
    FROM EmployeeLogin
)
SELECT
    EmployeeID,
    MIN(LoginDate) AS StartDate,
    MAX(LoginDate) AS EndDate,
    COUNT(*) AS ConsecutiveDays
FROM LoginGroups
GROUP BY
    EmployeeID,
    GroupID
ORDER BY StartDate;

Result

StartDateEndDateConsecutiveDays
2025-01-012025-01-033
2025-01-052025-01-062
2025-01-092025-01-091

Finding the Longest Streak

Suppose we only want the longest streak.

WITH LoginGroups AS
(
    SELECT
        EmployeeID,
        LoginDate,
        DATEADD(day,
            -ROW_NUMBER() OVER
            (
                PARTITION BY EmployeeID
                ORDER BY LoginDate
            ),
            LoginDate) AS GroupID
    FROM EmployeeLogin
),
Streaks AS
(
    SELECT
        EmployeeID,
        MIN(LoginDate) AS StartDate,
        MAX(LoginDate) AS EndDate,
        COUNT(*) AS ConsecutiveDays
    FROM LoginGroups
    GROUP BY EmployeeID, GroupID
)
SELECT TOP (1) *
FROM Streaks
ORDER BY ConsecutiveDays DESC;

Result

EmployeeIDStartDateEndDateConsecutiveDays
1012025-01-012025-01-033

Why Does This Technique Work?

Suppose we have three consecutive dates:

LoginDateRow Number
01-Jan1
02-Jan2
03-Jan3

Subtracting the row number from each date gives:

LoginDateLoginDate − RN
01-Jan31-Dec
02-Jan31-Dec
03-Jan31-Dec

Since the result is identical for every consecutive date, SQL Server can group them together automatically.

Whenever a gap occurs, the calculated value changes, creating a new group.

This elegant property makes the solution both simple and efficient.

Real-World Use Cases

This technique is useful for:

  • Employee attendance tracking
  • User login streaks
  • Website daily active users
  • IoT sensor uptime analysis
  • Machine maintenance monitoring
  • Sales activity tracking
  • Patient hospital visits
  • Consecutive purchase analysis
  • GitHub contribution streaks
  • Azure SQL monitoring reports

Performance Considerations

For large tables:

  • Create an index on (EmployeeID, LoginDate).
  • Avoid unnecessary sorting by matching the index order with the window function.
  • Use ROW_NUMBER() only after filtering the required data.
  • For very large datasets, review the execution plan to ensure the window function isn’t causing expensive sorts or spills.

Conclusion

The Gaps and Islands technique is one of the most valuable SQL patterns for analyzing consecutive data. By combining ROW_NUMBER() with simple date arithmetic, you can efficiently identify streaks, calculate their lengths, and answer a wide variety of business questions.

Whether you’re analyzing customer activity, employee attendance, or application usage, this pattern is an essential addition to every SQL developer’s toolkit.


Discover more from Technology with Vivek Johari

Subscribe to get the latest posts sent to your email.

Leave a Reply

Scroll to Top

Discover more from Technology with Vivek Johari

Subscribe now to keep reading and get access to the full archive.

Continue reading