MaxiWheat MaxiWheat - 2 months ago 6
SQL Question

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 ?

Answer
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;
Comments