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.
与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…