带有条件的唯一标识符字段


8

我有一个不在生产中的数据库,所以主表是CustodyDetails,此表有一个ID int IDENTITY(1,1) PRIMARY KEY列,我正在寻找一种添加另一个在其他任何表中都未引用的唯一标识符的方法。帐户列的内容将不完全是一个身份密钥。

这个新的身份列中有一些具体细节,这就是我的问题所在。格式如下:XX/YY其中XX是一个自动递增的值,它将在每个新年度重置/重新启动,而YY是本年度的后两位SELECT RIGHT(YEAR(GETDATE()), 2)

因此,例如,让我们假设从2015年12月28 03/01/2016结束的一天添加了一条记录,该列将如下所示:

ID    ID2     DATE_ADDED
1     1/15    2015-12-28
2     2/15    2015-12-29
3     3/15    2015-12-30
4     4/15    2015-12-31
5     1/16    2016-01-01
6     2/16    2016-01-02
7     3/16    2016-01-03

我想到了使用前端来解析组合ID(示例中为ID2)以获取最后2位数字并与当年的最后2位数字进行比较,然后决定是否启动新的相关项。当然,能够在数据库端完成所有操作将是宏伟的。

编辑1:顺便说一句,我也看到人们使用单独的表只是为了存储并行的身份密钥,所以一个表的身份密钥成为第二个表的辅助密钥,这听起来有点狡猾,但也许是这样的实现到位的情况?

编辑2:额外的 ID是旧文档参考,标记每个文件/记录。我猜想它可能是主ID的特殊别名。

在过去的20年中,该数据库每年处理的记录数没有超过100,并且极有可能(确实非常高),当然,如果超过99,该字段将能够继续使用多余的数字,前端/过程将能够超过99,因此它不会改变它。

当然,我一开始没有提到这些细节,因为它们只会缩小解决方案的可能性,以满足我的特定需求,并试图将问题范围扩大。


这是什么版本的SQL Server?
Max Vernon

如果不在任何地方用作参考,为什么需要将其存储在表中?为什么它不能是计算列(需要时在查询中保留还是计算)?如果一年中有超过100行,应该怎么办?
ypercubeᵀᴹ

1
对于ID= 5、6和7,DATE_ADDED应该是2016-01-01 ,依此类推?
Kin Shah

@金看起来像。我纠正了样品。
ypercubeᵀᴹ

感谢您的更正,是的,它们是2016年Recs,我现在使用的是SQL Server 2005。@YperSillyCubeᵀᴹ主要是寻找更好的解决方案,因此,任何建议都将不胜感激。
Nelz 2015年

Answers:


6

您可以使用密钥表来存储第二个ID列的递增部分。该解决方案不依赖任何客户端代码,并且可以自动识别多年。当@DateAdded参数传入先前未使用的年份时,它将ID2基于该年份自动开始使用该列的一组新值。因此,如果将proc用于插入前几年的行,则这些行将插入带有“正确”值的增量。该GetNextID()PROC是面向妥善处理可能出现的死锁,只有当试图更新时,会出现5个连续死锁传递一个错误给调用者tblIDs表。

创建一个表来存储每年包含当前使用的ID值的一行,以及一个存储过程以返回要使用的新值:

CREATE TABLE [dbo].[tblIDs]
(
    IDName nvarchar(255) NOT NULL,
    LastID int NULL,
    CONSTRAINT [PK_tblIDs] PRIMARY KEY CLUSTERED 
    (
        [IDName] ASC
    ) WITH 
    (
        PAD_INDEX = OFF
        , STATISTICS_NORECOMPUTE = OFF
        , IGNORE_DUP_KEY = OFF
        , ALLOW_ROW_LOCKS = ON
        , ALLOW_PAGE_LOCKS = ON
        , FILLFACTOR = 100
    ) 
);
GO

CREATE PROCEDURE [dbo].[GetNextID](
    @IDName nvarchar(255)
)
AS
BEGIN
    /*
        Description:    Increments and returns the LastID value from
                        tblIDs for a given IDName
        Author:         Max Vernon / Mike Defehr
        Date:           2012-07-19

    */
    SET NOCOUNT ON;

    DECLARE @Retry int;
    DECLARE @EN int, @ES int, @ET int;
    SET @Retry = 5;
    DECLARE @NewID int;
    WHILE @Retry > 0
    BEGIN
        SET @NewID = NULL;
        BEGIN TRY
            UPDATE dbo.tblIDs 
            SET @NewID = LastID = LastID + 1 
            WHERE IDName = @IDName;

            IF @NewID IS NULL
            BEGIN
                SET @NewID = 1;
                INSERT INTO tblIDs (IDName, LastID) 
                VALUES (@IDName, @NewID);
            END
            SET @Retry = -2; /* no need to retry since the 
                                  operation completed */
        END TRY
        BEGIN CATCH
            IF (ERROR_NUMBER() = 1205) /* DEADLOCK */
                SET @Retry = @Retry - 1;
            ELSE
                BEGIN
                SET @Retry = -1;
                SET @EN = ERROR_NUMBER();
                SET @ES = ERROR_SEVERITY();
                SET @ET = ERROR_STATE()
                RAISERROR (@EN,@ES,@ET);
                END
        END CATCH
    END
    IF @Retry = 0 /* must have deadlock'd 5 times. */
    BEGIN
        SET @EN = 1205;
        SET @ES = 13;
        SET @ET = 1
        RAISERROR (@EN,@ES,@ET);
    END
    ELSE
        SELECT @NewID AS NewID;
END
GO

您的表以及在其中插入行的proc:

CREATE TABLE dbo.Cond
(
    CondID INT NOT NULL
        CONSTRAINT PK_Cond
        PRIMARY KEY CLUSTERED
        IDENTITY(1,1)
    , CondID2 VARCHAR(30) NOT NULL
    , Date_Added DATE NOT NULL
);

GO
CREATE PROCEDURE dbo.InsertCond
(
    @DateAdded DATE
)
AS
BEGIN
    DECLARE @NextID INT;
    DECLARE @Year INT;
    DECLARE @IDName NVARCHAR(255);
    SET @Year = DATEPART(YEAR, @DateAdded);
    DECLARE @Res TABLE
    (
        NextID INT NOT NULL
    );
    SET @IDName = 'Cond_' + CONVERT(VARCHAR(30), @Year, 0);
    INSERT INTO @Res (NextID)
    EXEC dbo.GetNextID @IDName;

    INSERT INTO dbo.Cond (CondID2, Date_Added)
    SELECT CONVERT(VARCHAR(30), NextID) + '/' + 
        SUBSTRING(CONVERT(VARCHAR(30), @Year), 3, 2), @DateAdded
    FROM @Res;
END
GO

插入一些样本数据:

EXEC dbo.InsertCond @DateAdded = '2015-12-30';
EXEC dbo.InsertCond @DateAdded = '2015-12-31';
EXEC dbo.InsertCond @DateAdded = '2016-01-01';
EXEC dbo.InsertCond @DateAdded = '2016-01-02';

显示两个表:

SELECT *
FROM dbo.Cond;

SELECT *
FROM dbo.tblIDs;

结果:

在此处输入图片说明

密钥表和存储过程来自这个问题。


您正在设置事务隔离级别,但未明确打开事务。另外,如果两个并发会话尝试插入同一(IDName, LastID)行,是否会导致死锁或违反PK的事务之一?如果是后者,则为该交易提供另一个机会(这样它最终将获得ID 2)也许很有意义。
Andriy M 2015年

还有另一件事,我可能会@NewID在循环开始时显式设置为null:如果尝试插入行的事务成为死锁受害者,则它不会尝试在下一次迭代中插入行,因为@NewID已经有设置为1(非NULL,因此INSERT分支将被省略)。
Andriy M

实际上,根本不需要设置事务隔离级别。我将其删除。我看不到两个并发会话如何将相同的值插入到tblIDs表中,因为该表是通过单个原子操作更新的;“古怪”的更新。
Max Vernon

@NewID = NULL在循环开始时进行设置不是一个坏主意。
马克斯·弗农

我认为,从理论上讲,新年的第一个动作可能容易陷入僵局。
Max Vernon
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.