我可以在SQL Server中创建自定义唯一ID而不可能重复它吗?

时间:2018-03-16 23:07:27

标签: sql-server tsql

我需要生成具有以下格式的票证ID:

TicketType+YYMMDD+nnnn
  • TicketType为4个字符
  • YYMMDD是2位数的年/月/日
  • nnnn是一个4位数的递增数字,从0001开始,每个TicketType + YYMMDD

我有一些已经工作了一年的东西,但今天发现了一个缺陷。

DECLARE @TktType varchar(4) = CASE @TypeId WHEN 1 THEN 'TKTT' WHEN 2 THEN 'TKTD' WHEN 3 THEN 'TKTV' WHEN 4 THEN 'TKTB' END

DECLARE @DatePart varchar(6) = CAST(YEAR(GetDate()) - 2000 AS varchar(4)) + 
                               RIGHT('0' + CAST(MONTH(GetDate()) AS varchar(2)), 2) + 
                               RIGHT('0' + CAST(DAY(GetDate()) AS varchar(2)), 2)

DECLARE @nextNum varchar(4) = (SELECT CONVERT(INT, MAX(SUBSTRING(SO, 11, 4))) + 1 FROM T_SO WHERE SO LIKE @TktType + @DatePart +'%')

SET @nextNum = RIGHT('000' + COALESCE(@nextNum, '1'), 4)

INSERT INTO tblTickets (TktID, ...)
VALUES (@TktType + @DatePart + @nextNum, ...)

这已经工作了一年顺利。你能猜出发生了什么吗?今天有两个人在同一时间点击它。两者都生成了相同的Ticket ID,并且由于TktID列是主键,因此其中一个得到了一个很好的"违反PRIMARY KEY约束"消息。

所以我考虑过为每种票证类型创建一个带有标识列和位列的新表。插入0并返回插入的id。这意味着必须截断表并每隔午夜重置身份种子。我确定这有不可预见的问题。

我还考虑过循环和递增数字,直到插入成功为止。坏。

我的一位同事建议使用一个事务来锁定表,这会让其他人等到我完成。不确定。

有没有其他人必须做类似的事情?我正在寻找有关如何最好地解决问题的建议和意见。

编辑:我想我有一些有用的东西。随意留下你的想法。

首先,我创建了一个表,每个票证类型都有一行:

CREATE TABLE [dbo].[T_TicketID](
    [id] [int] NOT NULL,
    [TicketType] [varchar](4) NOT NULL PRIMARY KEY,
    [Date] [date] NOT NULL
)

然后我创建了一个接受故障单类型并返回完整故障单ID的过程:

ALTER PROCEDURE usp_CreateTicketID
    @TicketType varchar(4),
    @TicketID varchar(14) OUTPUT
AS

SET NOCOUNT ON

DECLARE @Date DATE = GETDATE()
DECLARE @out TABLE (TicketID varchar(14))

UPDATE T_TicketID
SET 
    id = CASE WHEN [Date] =  @Date THEN id + 1 ELSE 1 END,
    [Date] = CASE WHEN [Date] = @Date THEN [Date] ELSE @Date END
OUTPUT @TicketType + 
       CONVERT(varchar, YEAR(@Date) - 2000) + 
       RIGHT('0' + CONVERT(varchar, MONTH(@Date)), 2) + 
       RIGHT('0' + CONVERT(varchar, DAY(@Date)), 2) + 
       RIGHT('000' + CONVERT(varchar, INSERTED.id), 4) 
INTO @out 
WHERE TicketType = @TicketType

SET @TicketID = (SELECT TicketID FROM @out)

由于UPDATE是原子的,它会对更新进行序列化,每个人都会获得一个唯一的TicketID。

我通过2个进程对它进行了测试,每个进程在一个循环中,每个进程10,000次,循环没有延迟。我将生成的TicketID保存到表中,然后验证没有重复项。

2 个答案:

答案 0 :(得分:0)

如果您无法避免使用身份或GUID,则可以使用自定义序列来处理您遇到的竞争条件。 SQL Server将处理输出序列中下一个数字的困难部分,并使用cycle参数将它包围并在最后一个数字minvaluemaxvalue时返回使用

create sequence dbo.TicketNumber as smallint
start with 1
increment by 1
minvalue 1
maxvalue 9999
cycle;

-- This will give you the next value in line each time it's run
select next value for dbo.TicketNumber as TicketNumber

我建议您不要将TicketType,date和递增数字存储在一个字段中。如果每个值都有一列,并且主键是所有3列,例如:

,那会更好
create table dbo.tblTickets
(
    TicketType char(4) not null,
    TicketDate char(6) not null,
    TicketNumber smallint not null,
    constraint PK_TicketType_TicketDate_TicketNumber primary key
    (
        TicketType,
        TicketDate,
        TicketNumber
    )
)

答案 1 :(得分:0)

除非您有一天要插入9999张以上的门票,否则在您开始输入值100年之后,您的工作将会正常工作,那么2位数的年份将为您提供。

事务包装是您需要实施的唯一更改,以防止您遇到的情况。

虽然其他人不同意,但以这种方式制作主键很好,它适合您的使用案例,您知道它的局限性,并且它在您的业务案例中比随机guid更有意义。

使用内置的guid或整数标识可以阻止您发生的错误,但如果您当前的type-date-number键具有任何业务价值,您将失去该错误,或​​者仍然必须维持该约束同时还要保持指导。