Here is a solution that uses an identity column and triggers and in theory should work well in a multi-user environment (unlike solutions that read the current max invoice number).
I have an
invoice
table that contains at least:
create table invoice
(
id int identity(1,1),
invoicedate date,
invoiceNumber varchar(20)
)
Note that I have an
id
column that SQL is going to automatically increment for me but I also have an
invoiceNumber
column which will contain the contrived invoice number starting at 1 each year.
I also need a table to track the maximum
id
per year.
create table invoiceYears
(
invoiceYear int NOT NULL,
maxid int
)
I then create a TRIGGER on the invoice table to "calculate" the contrived invoiceNumber each time an invoice is inserted into the table.
CREEATE TRIGGER trig ON [dbo].invoice
AFTER INSERT
AS
declare @invid int;
declare @invyear int;
declare @invnum varchar(20);
select @invid=i.id from inserted i;
select @invyear=datepart(YEAR, i.invoicedate) from inserted i;
if exists (SELECT TOP 1 invoiceYear FROM invoiceYears WHERE invoiceYear = @invyear)
BEGIN
UPDATE invoiceYears set maxid = @invid WHERE invoiceYear = @invyear
END
ELSE
BEGIN
INSERT INTO invoiceYears values (@invyear, @invid)
END
UPDATE inv SET invoiceNumber = 'ABC-' + CAST(@invyear AS VARCHAR(4)) + '-' + CAST(inv.id - ISNULL(IY.maxid, 0) AS varchar(20))
FROM invoice inv
LEFT OUTER JOIN invoiceYears IY ON IY.invoiceYear = @invyear - 1
WHERE inv.id = @invid
GO
Note that it will insert a new row into this table at the change of the year. If your financial year does not start on 1st January of each year you will need to replace the
DATEPART
with the calculation of the financial year.
Other things to note:
- Because I've used a trigger then this isn't the most performant method to use in a high volume scenario.
- It won't work if you "skip" a year - unless you manually update the
invoiceYears
table
- I would normally leave the formatting of the invoice number to the presentation layer - if this is removed from the trigger then it performs better on inserts with only a minor extra overhead on reads.
- See the comments from Tomas above regarding transactions and locks - I haven't put these into the solution (for the sake of keeping as a simple example)