Search code examples
sqlsql-servert-sqlauto-increment

How to create an auto increment column that is segmented by an other column


I need to create a table that will contain a incremental id, but I would like the ids be automatically segmented according to an other column. Here is what I want :

CREATE TABLE dbo.MyTable (
    myKey INT IDENTITY PRIMARY KEY,
    category INT,
    incrementalId INT
);

INSERT INTO dbo.MyTable (category) VALUES (100);
INSERT INTO dbo.MyTable (category) VALUES (200);
INSERT INTO dbo.MyTable (category) VALUES (100);
INSERT INTO dbo.MyTable (category) VALUES (100);
INSERT INTO dbo.MyTable (category) VALUES (100);
INSERT INTO dbo.MyTable (category) VALUES (200);

SELECT *
FROM dbo.MyTable;

I would like this to display something like :

myKey       category    incrementalId
----------- ----------- -------------
1           100         1
2           200         1
3           100         2
4           100         3
5           100         4
6           200         2

Meaning I want the incrementalId to be automatically incremented per category and restart from 1 for any new category inserted. I want this to be done by itself on any inserts in the table (I don't want to have to remember to do that when I insert in this table).

I think this might be done with window functions and maybe a trigger, but I just can't figure how.

EDIT:

I would like the data to be persisted to avoid incrementalId to be shifted if data deletion happens. Also, ideally the same ID would not be re-given in the event of rows deletion (the same way that sequences or IDENTITY works)

Any idea ?


Solution

  • CREATE TABLE dbo.MyTable (
      myKey INT IDENTITY PRIMARY KEY,
      category INT,
      incrementalId INT
    );
    GO
    
    create table dbo.nextCategoryID (
      category int,
      nextidvalue int,
      constraint PK_nextCategoryID primary key clustered( category, nextidvalue )
    );
    GO
    
    create trigger numberByCategory on dbo.MyTable
    after insert as 
    
    -- Automatically add any net new category
    insert into dbo.nextCategoryID ( category, nextidvalue )
        select distinct category, 1 as nextidvalue
        from inserted
        where not exists ( select * from dbo.nextCategoryID s
            where s.category = inserted.category );
    
    
    -- Number the new rows in each incoming category
    with numberedrows as (
        select 
            i.myKey, 
            i.category, 
            n.nextidvalue - 1 + row_number() over ( partition by i.category order by i.category ) as incrementalId
        from inserted i
        join dbo.nextCategoryID n on i.category = n.category
    )
    update m
        set incrementalId = n.incrementalId
    from dbo.MyTable m
    join inserted i on m.myKey = i.myKey
    join numberedrows n on n.myKey = i.myKey;
    
    
    update dbo.nextCategoryID
        set nextidvalue = 1 + ( select max( m.incrementalId ) 
            from inserted i 
            join dbo.MyTable m on i.myKey = m.myKey
            where i.category = nextCategoryID.category 
        )
    where exists ( select * 
        from inserted i
        where i.category = nextCategoryID.category
    );
    
    GO
    
    -- Test data 
    
    INSERT INTO dbo.MyTable (category) VALUES (100);
    INSERT INTO dbo.MyTable (category) VALUES (200);
    INSERT INTO dbo.MyTable (category) VALUES (100);
    INSERT INTO dbo.MyTable (category) VALUES (100);
    INSERT INTO dbo.MyTable (category) VALUES (100);
    INSERT INTO dbo.MyTable (category) VALUES (200);
    
    insert into dbo.MyTable (category) 
    values 
        ( 200 ),
        ( 200 ),
        ( 100 ),
        ( 300 ),
        ( 400 ),
        ( 400 )
    
    
    SELECT *
    FROM dbo.MyTable;