将序列保存在特殊表中或获取最后一个值?

时间:2016-10-12 10:34:11

标签: sql-server

您是否有关于标识符序列的建议或最佳实践或建议?

我在数据库上工作,其中所有标识符或文档编号都是“复杂”序列。例如,我们的发票的顺序是INVCCC-2016-0000,其中INV是固定的,CCC是客户参考,2016是年,0000是1到9999的计数器。这个数字必须是唯一的,此时我们保留它列中。

当我创建新发票时,我需要检查今年为该客户创建的最后一个号码,然后将其增加一个,然后将我的数据保存在我的数据库中。

我认为有两种方法可以做到这一点

  1. 我创建了一个特殊的表,其中包含并维护每个客户端的所有上次使用的号码。每次我有一张新发票时,我都会检查此表中的号码,我将其增加一,我用这个号码来保存我的发票,然后我更新序列表。 1 READ,1 INSERT,1 UPDATE(可能是新的顺序INSERT)

    var keyType = "INV" + ClientPrefix + "-" + Year;
    var keyValue = Context.SequenceTable.SingleOrDefault(y => y.KeyType == keyType).KeyValue;
    
  2. 我检查发票表中的最后一个号码,然后我增加它然后保存我的发票。 1 READ,1 INSERT。我不需要更新另一个表,这对我来说似乎更合乎逻辑。但我的数据库管理员告诉我这可能会造成锁定或其他麻烦。

    var keyType = "INV" + ClientPrefix + "-" + Year;
    var keyValue = Invoices.Where(y => y.InvoiceId.StartsWith(keyType)).OrderByDescending(y => y.InvoiceId).LastOrDefault();
    
  3. 注意我在2015版本之前使用SQL Server,然后在SEQUENCE功能之前使用。我担心与解决方案1不一致。我担心解决方案2的性能问题。

1 个答案:

答案 0 :(得分:0)

我建议使用一个表来维护自定义序列的最后一个使用值。如果这是业务需求,此方法还将确保没有差距。

以下示例使用事务存储过程来避免在同一客户端同时生成发票编号的情况下出现竞争条件。如果您使用的是SQL Server 2012之前的版本,则需要将CATCH块更改为使用RAISERROR而不是THROW

CREATE TABLE InvoiceSequence (
      ClientReferenceCode char(3) NOT NULL
    , SequenceYear char(4) NOT NULL
    , SequenceNumber smallint NOT NULL
    CONSTRAINT PK_InvoiceSequence PRIMARY KEY (ClientReferenceCode, SequenceYear)
    );
GO

CREATE PROC dbo.GetNextInvoiceSequence
      @ClientReferenceCode char(3) = 'CCC'
    , @SequenceYear char(4) = '2016'
AS
SET XACT_ABORT, NOCOUNT ON;
DECLARE @SequenceNumber smallint;

BEGIN TRY

    BEGIN TRAN;

    UPDATE dbo.InvoiceSequence
    SET @SequenceNumber = SequenceNumber = SequenceNumber + 1
    WHERE
        ClientReferenceCode = @ClientReferenceCode
        AND SequenceYear = @SequenceYear;

    IF @@ROWCOUNT = 0
    BEGIN
        SET @SequenceNumber = 1;
        INSERT INTO dbo.InvoiceSequence(ClientReferenceCode, SequenceYear, SequenceNumber)
            VALUES (@ClientReferenceCode, @SequenceYear, @SequenceNumber);
    END;

    IF @SequenceNumber > 9999
    BEGIN
        RAISERROR('Invoice sequence limit reached for client %s year %s', 16, 1, @ClientReferenceCode, @SequenceYear) AS InvoiceNumber;
    END;

    COMMIT;

    SELECT 'INV' + @ClientReferenceCode + '_' + @SequenceYear + '_' + RIGHT('000' + CAST(@SequenceNumber AS varchar(4)), 4);

END TRY
BEGIN CATCH
    IF @@TRANCOUNT > 0 ROLLBACK;
    THROW;
END CATCH;
GO

--sample usage:
--note that year could be assigned in the proc if it is based on the current date
EXEC dbo.GetNextInvoiceSequence
      @ClientReferenceCode = 'CCC'
    , @SequenceYear = '2016';