格式为:YYYYNNNNNN的“ Id”,其中NNNNNN部分每年重新开始


11

我有一个业务需求,即发票表中的每个记录都有一个看起来像YYYYNNNNNN的ID。

NNNNNN部分需要在每年年初重新启动。因此,2016年输入的第一行看起来像2016000001,第二行看起来像2016000002等。假设2016年的最后一条记录是2016123456,下一行(2017年)应该看起来像2017000001

我不需要此ID作为主键,并且我也存储创建日期。这个想法是,这个“显示ID”是唯一的(因此我可以通过它查询),并且可以按年份分组。

任何记录都不太可能被删除;但是,我倾向于针对此类内容进行防御性编码。

有什么方法可以创建此ID,而不必在每次插入新行时都查询今年的最大ID?

想法:

  • A CreateNewInvoiceSP,获得当年的MAX值(讨厌)
  • 一些神奇的内置功能可以做到这一点(我可以梦到了)
  • 能够在IDENTITYor DEFAULT声明(??)中指定一些UDF或其他内容
  • 使用的视图PARTITION OVER + ROW()(被删除将是有问题的)
  • 触发INSERT(仍然需要运行一些MAX查询:()
  • 一年一次的后台工作,用每年插入的MAX来更新一张表格,然后我...有事吗?

所有这些都不理想。任何想法或变化都欢迎!


您有一些不错的答案,但是如果您有年份,则将id作为PK,那么选择max很快。
狗仔队

使用选择最大ID查询是一种常见的做法。用那个。
维吾尔族Gümüşhan

Answers:


17

您的领域有2个要素

  • 自动递增编号

它们不需要存储为一个字段

例:

  • 年份列,其默认值为 YEAR(GETDATE())
  • 基于序列的数字列。

然后创建一个将它们连接起来的计算列(采用适当的格式)。可以根据年份的变化重设顺序。

SQLfiddle中的示例代码:*(SQLfiddle并不总是有效)

-- Create a sequence
CREATE SEQUENCE CountBy1
    START WITH 1
    INCREMENT BY 1 ;

-- Create a table
CREATE TABLE Orders
    (Yearly int NOT NULL DEFAULT (YEAR(GETDATE())),
    OrderID int NOT NULL DEFAULT (NEXT VALUE FOR CountBy1),
    Name varchar(20) NOT NULL,
    Qty int NOT NULL,
    -- computed column
    BusinessOrderID AS RIGHT('000' + CAST(Yearly AS VARCHAR(4)), 4)
                     + RIGHT('00000' + CAST(OrderID AS VARCHAR(6)), 6),
    PRIMARY KEY (Yearly, OrderID)
    ) ;


-- Insert two records for 2015
INSERT INTO Orders (Yearly, Name, Qty)
    VALUES
     (2015, 'Tire', 7),
     (2015, 'Seat', 8) ;


-- Restart the sequence (Add this also to an annual recurring 'Server Agent' Job)
ALTER SEQUENCE CountBy1
    RESTART WITH 1 ;

-- Insert three records, this year.
INSERT INTO Orders (Name, Qty)
    VALUES
     ('Tire', 2),
     ('Seat', 1),
     ('Brake', 1) ;

1
也许每年有一个序列比较干净。这样,就无需将DDL作为常规操作的一部分来执行。
usr

@gbn所以我需要SEQUENCE 每年后台重新启动的后台作业吗?
DarcyThomas '16

@usr可悲的是您不能NEXT VALUE FORCASE声明中使用(我尝试过)
DarcyThomas

8

您是否考虑过创建一个种子= 2016000000的身份字段?

 create table Table1 (
   id bigint identity(2016000000,1),
   field1 varchar(20)...
)

该种子应该每年自动递增,例如,在您需要安排的2017年1月1日晚上

DBCC CHECKIDENT (Table1, RESEED, 2017000000)

但是我已经看到了设计方面的问题,例如:如果您有百万条记录怎么办?


2
另一个问题是记录是否没有按时间顺序显示。在这种情况下,身份可能不是走的路。
Daniel Hutmacher '16

@LiyaTansky在我的情况下,我被告知每年应该只有5万条记录。但我明白您的意思,因为它易碎,只有1kk行
DarcyThomas,2016年

1

在这种情况下,我所做的是将年份乘以10 ^ 6,然后将序列值添加到该值。这样做的优点是不需要计算字段(持续的开销很小),并且该字段可以用作PRIMARY KEY

有两种可能的陷阱:

  • 确保您的乘数足够大,以至于不会被耗尽,并且

  • 由于序列的高速缓存,您不能保证序列没有间隙。

我不是SQL Server方面的专家,但是您可能可以将事件设置为在201x 00:00:00触发,以将序列重置为零。这也是我在Firebird上所做的(或者是Interbase?)。


1

编辑:此解决方案在负载下不起作用

我不喜欢触发器,但这似乎可以解决。

优点:

  • 没有后台工作
  • 可以对DisplayId进行快速查询
  • 触发器不需要扫描先前的NNNNNN部分
  • 每年将重新启动NNNNN部分
  • 如果每年有超过100000行,则可以使用
  • 不需要架构更新(例如,序列重置)即可在将来继续工作

编辑:缺点:

  • 在负载下将失败(返回到绘图板)

(当我从他们的回答中得到一些启发时,请@gbn提供信用)(任何反馈并指出明显的错误表示欢迎:)

添加一些新COLUMN的和INDEX

ALTER TABLE dbo.Invoices
ADD     [NNNNNNId]      INT  NULL 

ALTER TABLE dbo.Invoices
ADD [Year]              int NOT NULL DEFAULT (YEAR(GETDATE()))

ALTER TABLE dbo.Invoices
ADD [DisplayId]     AS  'INV' +
                        CAST([Year] AS VARCHAR(4))+
                        RIGHT('00000' + CAST([NNNNNNId] AS VARCHAR(4)),  IIF (5  >= LEN([NNNNNNId]), 5, LEN([NNNNNNId])) )                  

EXEC('CREATE NONCLUSTERED INDEX IX_Invoices_DisplayId
ON dbo.Invoices (DisplayId)')

新增 TRIGGER

CREATE TRIGGER Invoices_DisplayId
ON dbo.Invoices
  AFTER  INSERT
AS 
BEGIN

SET NOCOUNT ON;    

UPDATE dbo.Invoices
SET NNNNNNId = CalcDisplayId
FROM (SELECT I.ID, IIF (Previous.Year = I.Year , (ISNULL(Previous.NNNNNNId,0) + 1), 1) AS CalcDisplayId  FROM
        (SELECT 
            ID  
           ,NNNNNNId 
           ,[year]
        FROM  dbo.Invoices
        ) AS Previous
    JOIN inserted AS I 
    ON Previous.Id = (I.Id -1) 
    ) X
WHERE 
   X.Id = dbo.Invoices.ID       
END
GO

我强烈建议您不要这样做。一旦您处于轻载状态,很可能会死锁并导致插入失败。您是否已将副本放入虚拟数据库中并一次打入数十个线程以进行插入(可能还选择/更新/删除),以查看会发生什么?
Cody Konior '16

@CodyKonior从根本上来说是有缺陷的,还是可以通过一些明智的锁定使其复活?如果没有,您将如何解决这个问题?
DarcyThomas '16

嗯 用10个线程运行。不知道它是否是死锁,但是我确实得到了一些比赛条件。在上一行触发器完成之前,一个触发器完成的位置。这将导致NULL输入一堆值。回到画板...
DarcyThomas 16-4-19的

当时灾难避免了:-)我的秘密是,我认识到大约五年前所做的事情的模式。我只知道,您在触发器内扫描表以查找下一个序列的方式会使负载增加。我不记得我是如何解决的,但是我以后可以检查。
Cody Konior '16

@CodyKonior我不认为它正在执行扫描(ON Previous.Id = (I.Id -1) 应该只是搜索),但是是的,仍然行不通。如果我可以在插入触发期间锁定表(?),那么我认为它将起作用。但这听起来也像是代码的味道。
DarcyThomas '16
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.