Skip to content
>_sqlbuddy

Consecutive Login Days

Find users who logged in on three or more consecutive calendar days.

Start practice

Problem statement

Prompt

Given the logins table below (one row per login, possibly several per user per day), write a query that returns every user who logged in on 3 or more consecutive calendar days.

  • Each qualifying user appears once, with a consecutive_days column showing the length of that consecutive run.
  • If a user has two separate runs of 3+ days, they appear once per run.
  • Multiple logins on the same day count as one day for streak purposes.
  • Only user_id and consecutive_days should be returned.

Example

user_idlogin_at
12024-01-01
12024-01-02
12024-01-03
22024-01-01
22024-01-03

Expected result:

user_idconsecutive_days
13

Constraints

  • logins has one row per login; a user can log in several times per day.
  • logins.login_at is a DATE and is not NULL.

Tips

  • Dedupe to one row per (user, day), then use the gaps and islands trick: login_at - ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY login_at) is constant for consecutive days.
  • In SQLite, subtract the row number with julianday(login_at) - rn or date(login_at, '-' || rn || ' days').
  • Group by (user, island key) and keep HAVING COUNT(*) >= 3.

Schema

CREATE TABLE logins (
    id       INTEGER PRIMARY KEY,
    user_id  INTEGER NOT NULL,
    login_at DATE NOT NULL
);

Sample data

INSERT INTO logins (id, user_id, login_at) VALUES
  (1,  1, '2024-01-01'),
  (2,  1, '2024-01-02'),
  (3,  1, '2024-01-03'),
  (4,  2, '2024-01-01'),
  (5,  2, '2024-01-03'),
  (6,  2, '2024-01-04'),
  (7,  3, '2024-01-01'),
  (8,  3, '2024-01-02'),
  (9,  3, '2024-01-04');

Additional hidden fixtures are applied during validation to test edge cases.

Solution

One correct approach — try solving it yourself in the practice editor first, then compare. There is usually more than one valid solution.

-- Reference: users with a run of 3+ consecutive login days (islands technique).
WITH deduped AS (
    SELECT DISTINCT user_id, login_at
    FROM logins
),
numbered AS (
    SELECT user_id, login_at,
           ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY login_at) AS rn
    FROM deduped
),
islands AS (
    SELECT user_id,
           date(login_at, '-' || rn || ' days') AS grp
    FROM numbered
)
SELECT user_id, COUNT(*) AS consecutive_days
FROM islands
GROUP BY user_id, grp
HAVING COUNT(*) >= 3
ORDER BY user_id, grp;

Key concepts

  • ROW_NUMBER
  • Gaps and islands
  • CTE
  • julianday
  • DISTINCT

Related questions

Learn more