在SQL中增加自定义主键值

时间:2014-01-02 12:39:19

标签: sql sql-server sql-server-2012

我被要求为主键列生成自定义ID值。查询如下,

SELECT * FROM SC_TD_GoodsInward WHERE EntityId = @EntityId
        SELECT @GoodsInwardId=IIF((SELECT COUNT(*) FROM SC_TD_GoodsInward)>0, (Select 'GI_'+RIGHT('00'+CONVERT(varchar,datepart(YY,getdate())),2)+RIGHT('00'+CONVERT(varchar,datepart(MM,getdate())),2)+RIGHT('00'+CONVERT(varchar,datepart(DD,getdate())),2)+'_'+CONVERT(varchar,@EntityId)+'_'+(SELECT RIGHT('0000'+CONVERT(VARCHAR,CONVERT(INT,RIGHT(MAX(GoodsInwardId),4))+1),4) from SC_TD_GoodsInward)), (SELECT 'GI_'+RIGHT('00'+CONVERT(varchar,datepart(YY,getdate())),2)+RIGHT('00'+CONVERT(varchar,datepart(MM,getdate())),2)+RIGHT('00'+CONVERT(varchar,datepart(DD,getdate())),2)+'_'+CONVERT(varchar,@EntityId)+'_0001')) 

这里SC_TD_GoodsInward是一个表,GoodsInwardId是要生成的值。我也得到了理想的输出。例子。

GI_131118_1_0001
GI_131212_1_0002
GI_131212_1_0003

但是,当最后一位数字达到 9999 时,上述条件会失败。我模拟了查询,结果是,

GI_131226_1_9997
GI_140102_1_9998
GI_140102_1_9999
GI_140102_1_0000
GI_140102_1_0000
GI_140102_1_0000
GI_140102_1_0000
GI_140102_1_0000

9999之后,它进入0000并且之后不再增加。因此,在将来,我最终会遇到PK重复错误。如何回收这些值,以便在9999之后,它继续为0000,1000 ......等。我在上述查询中缺少什么?

注意:请在查询中将@EntityId值视为1。 我正在使用SQL SERVER 2012。

4 个答案:

答案 0 :(得分:1)

您遇到此问题是因为一旦最后4位数字到达9999,无论插入了多少行,9999都将保持最高数字,并且您丢弃了最重要的数字

我会对此进行重新构建,以便在单独的计数器表(作为INTEGER)中跟踪GoodsInwardId的最后一次使用的INT部分值,然后根据需要将模数(%)设置为10000。如果存在对PK生成器的并发调用,请记住锁定计数器表行。

此外,即使您保留了所有数字(例如在其他字段中),请注意订购CHAR如下

1
11
2
22
3

然后应用MAX()将返回3,而不是22。

编辑 - 澄清计数器表替换

计数器表看起来像这样:

CREATE TABLE PK_Counters
(
    TableName NVARCHAR(100) PRIMARY KEY,
    LastValue INT
);

(您的@EntityID可能是反PK列的另一个候选人。)

然后在每次调用自定义PK密钥生成PROC时递增并获取适用的计数器:

UPDATE PK_Counters
SET LastValue = LastValue + 1
WHERE TableName = 'SC_TD_GoodsInward';

Select 
        'GI_'+RIGHT('00'+CONVERT(varchar,datepart(YY,getdate())),2)
        +RIGHT('00'+CONVERT(varchar,datepart(MM,getdate())),2)
        +RIGHT('00'+CONVERT(varchar,datepart(DD,getdate())),2)+'_'
        +CONVERT(varchar,@EntityId)+'_'
        +(SELECT RIGHT('0000'+ CONVERT(NVARCHAR, LastValue % 10000),4)
    FROM PK_Counters
    WHERE TableName = 'SC_TD_GoodsInward');

你也可以在计数器表中模拟LastValue(而不是在查询中),虽然我相信有更多关于通过离开计数器非模数插入的记录数的信息。

Fiddle here

Re:性能 - 通过PK从小表中选择一个整数值,然后应用modulo比从SUBSTRING选择MAX要快得多(这几乎肯定是扫描)

答案 1 :(得分:1)

DECLARE @entityid INT = 1;

SELECT ('GI_'
    + SUBSTRING(convert(varchar, getdate(), 112),3,6)    -- yymmdd today DATE 
    + '_' + CAST(@entityid AS VARCHAR(50)) + '_'        --@entity parameter
    + CASE MAX(t.GI_id + 1) --take last number + 1
        WHEN 10000 THEN             
            '0000' --reset
        ELSE
            RIGHT( CAST('0000' AS VARCHAR(4)) +
                   CAST(MAX(t.GI_id + 1) AS VARCHAR(4))
                   , 4) 
        END) PK
 FROM 
( 
    SELECT TOP 1
    CAST(SUBSTRING(GoodsInwardId,11,1) AS INT) AS GI_entity,
    CAST(SUBSTRING(GoodsInwardId,4,6) AS INT) AS GI_date,
    CAST(RIGHT(GoodsInwardId,4) AS INT) AS GI_id
    FROM SC_TD_GoodsInward
    WHERE CAST(SUBSTRING(GoodsInwardId,11,1) AS INT) = @entityid
    ORDER BY  gi_date DESC, rowTimestamp DESC, gi_id  DESC
) AS t

这应该采用最后的GoodInwardId记录,按日期DESC排序并取其数字“id”。然后添加+ 1以返回NEW ID并将其与今天的日期和您通过的@entityid相结合。如果> 9999,请从0000再次开始。

您需要一个时间戳类型列,以便在同一日期+相同的交易时间内订购两个。否则你可能会重复。

答案 2 :(得分:1)

在提出问题的解决方案之前,您的问题上几点:

  1. 由于Custom主键主要包括三个部分Date(140102),发生事务的物理位置(entityID),4个地点编号(9999)。
  2. 根据单个物理位置中单个日期的设计,不能超过9999个交易 - 我的解决方案也将包含相同的限制。
  3. 我的解决方案的一些要点

    1. 4位数字与日期相关,这表示计数从0000开始的新日期。例如 GI_140102_1_0001, GI_140102_1_0002, GI_140102_1_0003, GI_140103_1_0000, GI_140104_1_0000
    2. 此字段的任何方式都是唯一的。

      1. 该解决方案将记录中的最新日期与当前日期进行比较。 逻辑: 如果记录中的当前日期和最新日期匹配 然后它将值增加4位数值1 如果记录中的当前日期和最新日期不匹配 它将4位数字设置为值0000。
      2. 解决方案:(以下代码给出了下一个GoodsInwardId的值,根据要求使用它以适应您的解决方案)

        declare @previous nvarchar(30);
        declare @today nvarchar(30);
        declare @newID nvarchar(30);
        select @previous=substring(max(GoodsInwardId),4,6) from SC_TD_GoodsInward;
        Select @today=RIGHT('00'+CONVERT(varchar,datepart(YY,getdate())),2)
        +RIGHT('00'+CONVERT(varchar,datepart(MM,getdate())),2)+RIGHT('00'+CONVERT(varchar,datepart(DD,getdate())),2);
        
        if @previous=@today
        BEGIN
        Select @newID='GI_'+RIGHT('00'+CONVERT(varchar,datepart(YY,getdate())),2)
        +RIGHT('00'+CONVERT(varchar,datepart(MM,getdate())),2)+RIGHT('00'+CONVERT(varchar,datepart(DD,getdate())),2)
        +'_'+CONVERT(varchar,1)+'_'+(SELECT RIGHT('0000'+
        CONVERT(VARCHAR,CONVERT(INT,RIGHT(MAX(GoodsInwardId),4))+1),4) 
        from SC_TD_GoodsInward);
        END
        else
        BEGIN
        SET @newID='GI_'+RIGHT('00'+CONVERT(varchar,datepart(YY,getdate())),2)
        +RIGHT('00'+CONVERT(varchar,datepart(MM,getdate())),2)+RIGHT('00'+CONVERT(varchar,datepart(DD,getdate())),2)
        +'_'+CONVERT(varchar,1)+'_0000';
        END
        select @newID;
        

        T-SQL创建所需的结构(可能的猜测)

        表格:

        CREATE TABLE [dbo].[SC_TD_GoodsInward](
            [EntityId] [int] NULL,
            [GoodsInwardId] [nvarchar](30) NULL
        )
        

        表的样本记录:

        insert into dbo.SC_TD_GoodsInward values(1,'GI_140102_1_0000');
        insert into dbo.SC_TD_GoodsInward values(1,'GI_140101_1_9999');
        insert into dbo.SC_TD_GoodsInward values(1,'GI_140101_1_0001');
        

        **在您的情况下它是一个可能的解决方案,尽管完美的解决方案是拥有标识列(如果需要,使用重新设置)并将其与当前日期绑定为计算列。

答案 3 :(得分:0)

我已经更简化了答案,并通过以下查询到达。

IF (SELECT COUNT(GoodsInwardId) FROM SC_TD_GoodsInward WHERE EntityId = @EntityId)=0
BEGIN
SELECT @GoodsInwardId=  'GI_'+RIGHT('00'+CONVERT(varchar,datepart(YY,getdate())),2)+
                            RIGHT('00'+CONVERT(varchar,datepart(MM,getdate())),2)+
                            RIGHT('00'+CONVERT(varchar,datepart(DD,getdate())),2)+'_'+
                            CONVERT(varchar,@EntityId)+'_0001'
END
ELSE
BEGIN
SELECT * FROM SC_TD_GoodsInward WHERE EntityId = @EntityId AND CONVERT(varchar,CreatedOn,103) = CONVERT(varchar,GETDATE(),103)
SELECT @GoodsInwardId=IIF(@@ROWCOUNT>0, 
                            (Select 'GI_'+
                            RIGHT('00'+CONVERT(varchar,datepart(YY,getdate())),2)+
                            RIGHT('00'+CONVERT(varchar,datepart(MM,getdate())),2)+
                            RIGHT('00'+CONVERT(varchar,datepart(DD,getdate())),2)+'_'+
                            CONVERT(varchar,@EntityId)+'_'+
                            (SELECT RIGHT('0000'+CONVERT(VARCHAR,CONVERT(INT,RIGHT(MAX(GoodsInwardId),4))+1),4) from SC_TD_GoodsInward WHERE CONVERT(varchar,CreatedOn,103) = CONVERT(varchar,GETDATE(),103))), 

                            (SELECT 'GI_'+RIGHT('00'+CONVERT(varchar,datepart(YY,getdate())),2)+
                            RIGHT('00'+CONVERT(varchar,datepart(MM,getdate())),2)+
                            RIGHT('00'+CONVERT(varchar,datepart(DD,getdate())),2)+'_'+
                            CONVERT(varchar,@EntityId)+'_0001')) 
END

select * from SC_TD_GoodsInward