Search code examples
sqlsql-servert-sqlwindow-functionsgaps-and-islands

Count number of consecutive grouped entries in SQL


I'd like to create and populate the following No. of Entries in Curr.Status field seen below using SQL (sql server).

ID          Sequence    Prev.Status Curr.Status No. of Entries in Curr.Status
9-9999-9    1           Status D    Status A    1
9-9999-9    2           Status A    Status A    2
9-9999-9    3           Status A    Status A    3
9-9999-9    4           Status A    Status A    4
9-9999-9    5           Status A    Status B    1
9-9999-9    6           Status B    Status B    2
9-9999-9    7           Status B    Status B    3
9-9999-9    8           Status B    Status A    1
9-9999-9    9           Status A    Status A    2
9-9999-9    10          Status A    Status C    1
9-9999-9    11          Status C    Status C    2

Is there an quick way using something like row_number() --this alone doesn't appear to be sufficient-- to create the field I'm looking for?

Thanks!


Solution

  • You can mark the rows where status changes using LAG function, and use SUM() OVER () to assign unique number to each group. Numbering within group is trivial:

    DECLARE @t TABLE (ID VARCHAR(100), Sequence INT, PrevStatus VARCHAR(100), CurrStatus VARCHAR(100));
    INSERT INTO @t VALUES
    ('9-9999-9',  1, 'Status D', 'Status A'),
    ('9-9999-9',  2, 'Status A', 'Status A'),
    ('9-9999-9',  3, 'Status A', 'Status A'),
    ('9-9999-9',  4, 'Status A', 'Status A'),
    ('9-9999-9',  5, 'Status A', 'Status B'),
    ('9-9999-9',  6, 'Status B', 'Status B'),
    ('9-9999-9',  7, 'Status B', 'Status B'),
    ('9-9999-9',  8, 'Status B', 'Status A'),
    ('9-9999-9',  9, 'Status A', 'Status A'),
    ('9-9999-9', 10, 'Status A', 'Status C'),
    ('9-9999-9', 11, 'Status C', 'Status C');
    
    WITH cte1 AS (
        SELECT *, CASE WHEN LAG(CurrStatus) OVER(ORDER BY Sequence) = CurrStatus THEN 0 ELSE 1 END AS chg
        FROM @t
    ), cte2 AS (
        SELECT *, SUM(chg) OVER(ORDER BY Sequence) AS grp
        FROM cte1
    ), cte3 AS (
        SELECT *, ROW_NUMBER() OVER(PARTITION BY grp ORDER BY Sequence) AS SeqInGroup
        FROM cte2
    )
    SELECT *
    FROM cte3
    ORDER BY Sequence
    

    Demo on DB Fiddle