Generate unique invoice number-very CRITICAL application.

  • dear friends,

    I need to generate invoice number(s). To keep everything easy lets say I need to generate incremental number for each customer- starting with 1 and increment by 1. I cannot use ID column, don't ask why, but for example after delete operations I cannot take ID counter back.

    I see some new functions of sql2012 and sp's when googling but my problem is:

    this is a credit card transaction application. invoice number CANNOT be used 2 times, even once, everyone gets into trouble. not using one number twice is very CRITICAL. I cannot emphasize enough how critical it is.

    I need reliable, known solution that I can trust. it seems to me that as this is a asp.net application and will be under heavy usage possibility of any algorithm that generates number without table locking would cause problems. -at least I think that way, maybe wrong.

    how can I achieve that ? how do you deal with invoice numbers ? db will be sql2012, all new functions are welcome without compatibility concern. I can use stored procedures or use aspnet code, not important.

    best regards.

  • I guess it all depends on what is meant by, "I cannot use ID column, don't ask why, but for example after delete operations I cannot take ID counter back."

    You can use a one row control file like so...

    First create the table:

    CREATE TABLE [dbo].[controlFile](

    [number] [bigint] NULL

    ) ON [PRIMARY]

    Then, create a stored proc NewNumber

    create procedure NewNumber

    as

    begin

    update controlfile set number = number + 1

    select number from controlfile

    end

    As long as you always use it inside a transaction, numbers will always be sequential and there will be no gaps. That being said, you now have to make sure everything that creates one of those numbers is in a transaction or there will be problems. If you were doing it in a website that ran on a single server, you could do it in code using the lock statement, but that wouldn't persist the key between starts and would cause problems if you went to a web farm.

  • here's the question: (as I don't know sp's very well, or lest's say I have trust issues against sql server-yet)

    is there any chance of 2 insert statements getting same number if they occur exactly same time ? I mean:

    visiort1 presses button, application executes sp and gets generated number.

    visitor2 makes same thing exactly at the same millisecond.

    I'm asking this because in real life of this application its very possible the button_click event to be fired 100 times in any given second. and more than that, visitors can start "batch" operations that can runs thousands of insert statements. lets say I wrote an insert trigger and I added these generated numbers to records.

    I know, if this kind of problem would be possible then sql server engine wouldn't be a real database engine but I need to confirm this:

    if I wrote an insert trigger that adds invoice number to the inserted data, even if millions of transactions would be run at the exact same millisecond, sql server would handlesthem and works perfectly.

    can you confirm that please ? more importantly, did you every experienced this ?

  • lnardozi 61862 (11/4/2012)


    I guess it all depends on what is meant by, "I cannot use ID column, don't ask why, but for example after delete operations I cannot take ID counter back."

    You can use a one row control file like so...

    First create the table:

    CREATE TABLE [dbo].[controlFile](

    [number] [bigint] NULL

    ) ON [PRIMARY]

    Then, create a stored proc NewNumber

    create procedure NewNumber

    as

    begin

    update controlfile set number = number + 1

    select number from controlfile

    end

    As long as you always use it inside a transaction, numbers will always be sequential and there will be no gaps. That being said, you now have to make sure everything that creates one of those numbers is in a transaction or there will be problems. If you were doing it in a website that ran on a single server, you could do it in code using the lock statement, but that wouldn't persist the key between starts and would cause problems if you went to a web farm.

    If you do this in a transaction, I guarantee (I've been through this before) hundreds and maybe even thousands of deadlocks per day and each deadlock will cause... a Gap! If there's a rollback, you'll still get a Gap in the data.

    One way to do this is to do extreme validation of the data in the transaction to ensure that that transaction will succeed and use an IDENTITY column. Of course, even that can fail if you have a limit on the account and more than one person has a card against the account. Although not likely, it IS possible for one transaction to get in between the time you validate and the time you insert.

    The best way to do this is take any and all "attempts" (using an IDENTITY column) and ensure no chance of a rollback. Then validate the transaction once it's been recorded. If the transaction is no good, then mark the transaction as "invalid" and leave it where it stands.

    --Jeff Moden


    RBAR is pronounced "ree-bar" and is a "Modenism" for Row-By-Agonizing-Row.
    First step towards the paradigm shift of writing Set Based code:
    ________Stop thinking about what you want to do to a ROW... think, instead, of what you want to do to a COLUMN.

    Change is inevitable... Change for the better is not.


    Helpful Links:
    How to post code problems
    How to Post Performance Problems
    Create a Tally Function (fnTally)

  • then if I have 10 customers, I have to have 10 invoice number tables for each of them to keep different invoice numbers for different customers right ?

    as I understood after all reading the most reliable method is identification column.

    I just tested the above solution with SQLQueryStress -which is very simple tool. 1,000,000 transaction with 200 threads.

    with 1,000,000 inserts, almost 131,000 numbers shows up more than once. some of them 2 or tree, some of them even 20. (I use 16gb imac computer- which is fast)

  • By "customers" I'm guessing you mean "unique sets of invoice numbers". If that is what you mean, you just need an additional column in the same table for each set of invoice numbers you want to keep. If by "customer" you mean the end buyer then I'm completely lost as to what you're trying to accomplish.

  • putting the proc inside a transaction eliminates that possibility.

  • first answer is yes, if I have 10 customers, I need 10 set of invoice numbers.

    the transaction I use is:

    declare @no int

    exec CreateInvoiceNumber @no output

    select @no

    insert into test (no) VALUES( @no)

    and this is the sp:

    CREATE procedure [dbo].[CreateInvoiceNumber] (@no INT OUTPUT)

    as

    begin

    declare @x int

    update InvoiceNumbers set number = number + 1

    select @x = (select number from InvoiceNumbers)

    set @no = @x

    end

    the I look into test table to see if there are any duplicate records.

    if you suggest any changes I would appreciate.

  • Odd. I just tried it in the exact same tool and it performed flawlessly. You did use the proc inside another proc - and in that proc it was between a begin transaction and a commit? Unless it was part of an implicit transaction, that's what the whole ACID thing is about - either it happens or it doesn't.

  • lnardozi 61862 (11/4/2012)


    putting the proc inside a transaction eliminates that possibility.

    Yes it does... it also guarantees deadlocks.

    --Jeff Moden


    RBAR is pronounced "ree-bar" and is a "Modenism" for Row-By-Agonizing-Row.
    First step towards the paradigm shift of writing Set Based code:
    ________Stop thinking about what you want to do to a ROW... think, instead, of what you want to do to a COLUMN.

    Change is inevitable... Change for the better is not.


    Helpful Links:
    How to post code problems
    How to Post Performance Problems
    Create a Tally Function (fnTally)

  • aykut canturk (11/4/2012)


    as I understood after all reading the most reliable method is identification column.

    Same reliability as the new SEQUENCE numbers (like Oracle has) available in SQL Server 2012 but easier to use.

    --Jeff Moden


    RBAR is pronounced "ree-bar" and is a "Modenism" for Row-By-Agonizing-Row.
    First step towards the paradigm shift of writing Set Based code:
    ________Stop thinking about what you want to do to a ROW... think, instead, of what you want to do to a COLUMN.

    Change is inevitable... Change for the better is not.


    Helpful Links:
    How to post code problems
    How to Post Performance Problems
    Create a Tally Function (fnTally)

  • wrong post.

  • hmmm, begin/commit transaction looks like did the trick.

    begin transaction

    declare @no int

    exec CreateInvoiceNumber @no output

    select @no

    insert into test (no) VALUES( @no)

    commit transaction

    1.000.000 transactions, 200 threds, no duplicates.

    thanks... that was educational conversation.

  • It occurred to me later it could be simplified to

    insert into table_that_needs_number (number) select a.number from (update controlfile set number= number+1 output inserted.number) a

    seems to me that would be kind of deadlock proof - at least in my test with 200 threads 1000 reps it never once deadlocked, even using implicit transactions.

  • Having thought about the possibility of deadlock, I think it might be better to go like this - puts it all in one sql statement.

    CREATE TABLE [dbo].[controlFile](

    [Client1Number] [bigint] NULL default 0, --you'd probably use the name of the client

    [Client2Number] [bigint] NULL default 0, --this is the only time I can think of where

    [Client3Number] [bigint] NULL default 0, --a primary key is counterprductive

    [Client4Number] [bigint] NULL default 0,

    [Client5Number] [bigint] NULL default 0,

    [Client6Number] [bigint] NULL default 0,

    [Client7Number] [bigint] NULL default 0,

    [Client8Number] [bigint] NULL default 0,

    [Client9Number] [bigint] NULL default 0,

    ) ON [PRIMARY]

    Then your key statement would be like this:

    insert into table_that_needs_number (number) select a.Client1Number from (update controlfile set Client1Number = Client1Number +1 output inserted.Client1Number ) a

Viewing 15 posts - 1 through 15 (of 37 total)

You must be logged in to reply to this topic. Login to reply