sql server:根据计数器和另一个列值生成主键

时间:2011-01-27 21:01:54

标签: sql sql-server tsql

我正在创建一个包含公司父表的客户表。 已经指出(懊恼)我将为customer表创建一个主键,它是公司id的组合,它是customer表中现有的varchar(4)列,例如, customer.company

varchar(9)主键的其余部分应为零填充计数器,递增该公司内的客户数量。

E.g。 company = MSFT,这是MSFT记录的第一个插入:PK应为MSFT00001 在随后的插入中,PK将是MSFT00001,MSFT00002等。 然后当company = INTL并插入其第一条记录时,第一条记录将是INTL00001

我从一个触发器和一个我从其他stackoverflow响应创建的udf开始。

ALTER FUNCTION [dbo].[GetNextID]
(
  @in varchar(9)
)
RETURNS varchar(9) AS
BEGIN
    DECLARE @prefix varchar(9);
    DECLARE @res varchar(9);
    DECLARE @pad varchar(9);
    DECLARE @num int;
    DECLARE @start int;


if LEN(@in)<9


 begin
   set @in = Left(@in + replicate('0',9) , 9)
  end

SET @start = PATINDEX('%[0-9]%',@in);
SET @prefix = LEFT(@in, @start - 1 );


declare @tmp int;
 set @tmp = len(@in)
 declare @tmpvarchar varchar(9);
 set @tmpvarchar = RIGHT( @in, LEN(@in) - @start + 1 )
    SET @num = CAST(  RIGHT( @in, LEN(@in) - @start + 1 ) AS int  ) + 1
    SET @pad = REPLICATE( '0', 9 - LEN(@prefix) - CEILING(LOG(@num)/LOG(10)) );
    SET @res = @prefix + @pad + CAST( @num AS varchar);

    RETURN @res
END

如何编写my而不是触发器来插入值并增加此主键。或者我应该放弃它并开始一个割草业务?

对不起,tmpvarchar变量SQL服务器在没有它的情况下给了我奇怪的结果。

2 个答案:

答案 0 :(得分:2)

虽然我同意反对者的观点,但“接受不可改变的原则”的原则往往会降低整体压力水平,恕我直言。尝试以下方法。

<强>缺点

  • 仅限单行插入。您不会对新客户表进行任何批量插入,因为每次要插入行时都需要执行存储过程。
  • 密钥生成表的一定量争用,因此可能会阻塞。

但是,在这方面,这种方法并没有任何与之相关的竞争条件,而且真正冒犯我的敏感度并不是太令人震惊。所以......

首先,从密钥生成表开始。它将包含每个公司的1行,包含您的公司标识符和一个整数计数器,我们将在每次执行插入时碰撞。

create table dbo.CustomerNumberGenerator
(
  company     varchar(8) not null ,
  curr_value  int        not null default(1) ,

  constraint CustomerNumberGenerator_PK primary key clustered ( company ) ,

)

其次,您需要这样的存储过程(实际上,您可能希望将此逻辑集成到负责插入客户记录的存储过程中。稍后详细介绍)。该存储过程接受公司标识符(例如“MSFT”)作为其唯一参数。此存储过程执行以下操作:

  • 将公司ID放入规范形式(例如大写和修剪前导/尾随空格)。
  • 如果该行尚不存在(原子操作),则将该行插入密钥生成表。
  • 在单个原子操作(更新语句)中,获取指定公司的计数器的当前值,然后递增。
  • 然后以指定的方式生成客户编号,并通过1行/ 1列SELECT语句返回给调用者。

你走了:

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

您可能希望将其集成到将行插入customer表的存储过程中的原因是它将它们全部合并到一个事务中;如果没有这个,当插入失败时,您的客户编号可能会/将会出现间隙。

答案 1 :(得分:0)

正如其他人在我之前所说的那样,使用带有计算自动增量值的主键听起来是一个非常糟糕的主意!

如果您被允许并且如果您能够忍受下行(见下图),我建议如下:

使用普通数字自动增量键和仅包含公司ID的char(4)列 然后,当您从表中选择时,在自动增量列上使用row_number并将其与公司ID组合,以便您有一个带有“key”的附加列,看起来像您想要的那样(MSFT00001,MSFT00002,... )

示例数据:

create table customers
(
    Id int identity(1,1) not null,
    Company char(4) not null,
    CustomerName varchar(50) not null
)

insert into customers (Company, CustomerName) values ('MSFT','First MSFT customer')
insert into customers (Company, CustomerName) values ('MSFT','Second MSFT customer')
insert into customers (Company, CustomerName) values ('ABCD','First ABCD customer')
insert into customers (Company, CustomerName) values ('MSFT','Third MSFT customer')
insert into customers (Company, CustomerName) values ('ABCD','Second ABCD customer')

这将创建一个如下所示的表:

Id   Company CustomerName
------------------------------------
1   MSFT    First MSFT customer
2   MSFT    Second MSFT customer
3   ABCD    First ABCD customer
4   MSFT    Third MSFT customer
5   ABCD    Second ABCD customer

现在对其运行以下查询:

select 
    Company + right('00000' + cast(ROW_NUMBER() over (partition by Company order by Id) as varchar(5)),5) as SpecialKey,
    * 
from
    customers

这将返回相同的表格,但附加了一个带有“特殊键”的列:

SpecialKey   Id  Company CustomerName
---------------------------------------------
ABCD00001   3   ABCD    First ABCD customer
ABCD00002   5   ABCD    Second ABCD customer
MSFT00001   1   MSFT    First MSFT customer
MSFT00002   2   MSFT    Second MSFT customer
MSFT00003   4   MSFT    Third MSFT customer

您可以使用此查询创建一个视图,并让每个人都使用该视图,以确保每个人都能看到“特殊键”列。

然而,这个解决方案有两个缺点:

  1. 至少需要SQL Server 2005 命令row_number起作用。
  2. 当您从表中删除公司时,特殊键中的数字将会更改。因此,如果您不希望更改数字,则必须确保从该表中删除任何内容。