sql server: generate primary key based on counter and another column value

后端 未结 2 1356
北恋
北恋 2020-12-21 15:32

I am creating a customer table with a parent table that is company. It has been dictated(chagrin) that I shall create a primary key for the customer table that is a combinat

2条回答
  •  予麋鹿
    予麋鹿 (楼主)
    2020-12-21 15:42

    Whilst I agree with the naysayers, the principle of "accepting that which cannot be changed" tends to lower the overall stress level, IMHO. Try the following approach.

    Disadvantages

    • Single-row inserts only. You won't be doing any bulk inserts to your new customer table as you'll need to execute the stored procedure each time you want to insert a row.
    • A certain amount of contention for the key generation table, hence a potential for blocking.

    On the up side, though, this approach doesn't have any race conditions associated with it, and it isn't too egregious a hack to really and truly offend my sensibilities. So...

    First, start with a key generation table. It will contain 1 row for each company, containing your company identifier and an integer counter that we'll be bumping up each time an insert is performed.

    create table dbo.CustomerNumberGenerator
    (
      company     varchar(8) not null ,
      curr_value  int        not null default(1) ,
    
      constraint CustomerNumberGenerator_PK primary key clustered ( company ) ,
    
    )
    

    Second, you'll need a stored procedure like this (in fact, you might want to integrate this logic into the stored procedure responsible for inserting the customer record. More on that in a bit). This stored procedure accepts a company identifier (e.g. 'MSFT') as its sole argument. This stored procedure does the following:

    • Puts the company id into canonical form (e.g. uppercase and trimmed of leading/trailing whitespace).
    • Inserts the row into the key generation table if it doesn't already exist (atomic operation).
    • In a single, atomic operation (update statement), the current value of the counter for the specified company is fetched and then incremented.
    • The customer number is then generated in the specified way and returned to the caller via a 1-row/1-column SELECT statement.

    Here you go:

    create procedure dbo.GetNewCustomerNumber
    
      @company         varchar(8)
    
    as
    
      set nocount                 on
      set ansi_nulls              on
      set concat_null_yields_null on
      set xact_abort              on
    
      declare
        @customer_number varchar(32)
    
      --
      -- put the supplied key in canonical form
      --
      set @company = ltrim(rtrim(upper(@company)))
    
      --
      -- if the name isn't already defined in the table, define it.
      --
      insert dbo.CustomerNumberGenerator ( company )
      select id = @company
      where not exists ( select *
                         from dbo.CustomerNumberGenerator
                         where company = @company
                       )
    
      --
      -- now, an interlocked update to get the current value and increment the table
      --
      update CustomerNumberGenerator
      set @customer_number = company + right( '00000000' + convert(varchar,curr_value) , 8 ) ,
          curr_value       = curr_value + 1
      where company = @company
    
      --
      -- return the new unique value to the caller
      --
      select customer_number = @customer_number
      return 0
    
    go
    

    The reason you might want to integrate this into the stored procedure that inserts a row into the customer table is that it makes globbing it all together into a single transaction; without that, your customer numbers may/will get gaps when an insert fails land gets rolled back.

提交回复
热议问题