web-dev-qa-db-ja.com

シーケンスを毎年リセットする

以下の表に新しい行が追加されるたびに、シーケンス(インポート許可番号)を1 .... 20160001、20160002などから1つずつ増やし、新しい年には20170001、20170002などにリセットします。

CREATE TABLE [dbo].[tblPermits](
[ImportPermitID] [int] IDENTITY(1,1) NOT NULL,
[ImportPermitNo] [nchar](20) NULL,
[ImporterName] [int] NULL,
[Province] [varchar](50) NULL,
[LodgementDate] [datetime] NULL,
[PortofEntry] [int] NOT NULL,
[EstDateofArrival] [datetime] NULL,
[ConsignmentInvoicePONo] [varchar](50) NULL,
[OtherImportConditions] [varchar](400) NULL,
[Supplier] [int] NOT NULL,
[SupplierCountry] [varchar](50) NULL,
[CountryofOrigion] [int] NOT NULL,
CONSTRAINT [PK_tblPermits] PRIMARY KEY CLUSTERED 
(
[ImportPermitID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]

GO

現在私が持っているのは以下に示すようなトリガーです。

ALTER TRIGGER [dbo].[trgPermitsInsertImportPermitNo]
ON [dbo].[tblPermits] FOR INSERT
AS 
UPDATE dbo.tblPermits
SET ImportPermitNo = 'IP' + CAST(YEAR(i.LodgementDate) AS CHAR(4)) + RIGHT('000000' + CAST(i.ImportPermitID AS VARCHAR(6)), 6) 
FROM dbo.tblPermits p
INNER JOIN INSERTED i ON p.ImportPermitID = i.ImportPermitID

しかし、上記のトリガーのシーケンスを新しい年に0001にリセットすることができませんでした。

新しい年にシーケンスをリセットするようにトリガーを変更するにはどうすればよいですか?

  • このテーブルから行を削除できます。その場合、他の行は再番号付けされません。
  • 1年の新しい行の数は5000未満になると予想されますが、いずれの場合も、より広い範囲に対応するために、最終的な設計で接頭辞ゼロの数を増やすことができます。
  • 宿泊日は、ユーザーが入力する通常の日時フィールドです。
  • すべてのエントリは、クライアントから受信するとすぐに作成されます。前年度の日付が遅くなることは問題になりません。
  • ImportPermitNo値の重複は許可されていません。
5
Chris

SQL Server 2012以降では、これを sequence objects を使用して実装します。

SQL Server 2008 R2の場合、欠けている機能の代わりに シーケンステーブル を使用します。この場合、各年のマスターシーケンステーブルにキーがあります。次に例を示します。

CREATE TABLE dbo.SequenceTable
(
    sequence_name   nvarchar(20) NOT NULL,
    next_value      integer NOT NULL,

    CONSTRAINT [PK dbo.SequenceTable sequence_name]
    PRIMARY KEY CLUSTERED (sequence_name),
);
GO
INSERT dbo.SequenceTable
    (sequence_name, next_value)
VALUES
    (N'PermitIDs for 2016', 1),
    (N'PermitIDs for 2017', 1),
    (N'PermitIDs for 2018', 1);

シーケンスからキーまたはキーの範囲を確実に割り当てる標準の割り当てストアドプロシージャは次のとおりです。

CREATE PROCEDURE dbo.Allocate_TSQL

    @SequenceName       nvarchar(20),   -- The name of the sequence to allocate keys from
    @RangeSize          integer = 1,    -- The number of keys to allocate
    @FirstAllocated     integer OUTPUT  -- The first key allocated (output)

AS
BEGIN

    SET XACT_ABORT ON;  -- Most errors will abort the batch
    SET NOCOUNT ON;     -- Supress 'x row(s) affected' messages
    SET ROWCOUNT 0;     -- Reset rowcount

    -- Validate the range size requested
    IF (@RangeSize IS NULL OR @RangeSize < 1)
    BEGIN
        RAISERROR('@RangeSize must be a positive integer (supplied value = %i)', 16, 1, @RangeSize);
        RETURN  999;
    END;

    -- Initialize the output parameter
    SET @FirstAllocated = NULL;

    -- Update the row associated with @SequenceName, returning the 
    -- current value, and then incrementing it by @RangeSize
    UPDATE dbo.SequenceTable WITH (READCOMMITTEDLOCK)
    SET @FirstAllocated = next_value,
        next_value = next_value + @RangeSize
    WHERE sequence_name = @SequenceName;

    -- If @Allocated has a non-NULL value, we know we successfully updated a row
    RETURN CASE WHEN (@FirstAllocated IS NOT NULL) THEN 0 ELSE -999 END; 
END;

次に、質問の表の簡略版を考えます。

CREATE TABLE dbo.ImportPermits
(
    ImportPermitID integer IDENTITY (1, 1)
        CONSTRAINT [PK dbo.ImportPermits ImportPermitID]
        PRIMARY KEY CLUSTERED,
    ImportPermitNo nchar(12) NULL,
    LodgementDate datetime NULL
);

次のトリガーを使用して、1年あたりのシーケンス番号を割り当てることができます。

CREATE TRIGGER ImportPermitsImportPermitNo
ON dbo.ImportPermits
AFTER INSERT AS
BEGIN
    IF @@ROWCOUNT = 0 RETURN;   -- Return immediately if no rows affected
    SET XACT_ABORT, NOCOUNT ON; -- Most errors abort the batch; no row count messages
    SET ROWCOUNT 0;             -- Ensure all rows are visible (local to trigger)

    DECLARE 
        @rc integer,
        @FirstAllocated integer,
        @RowCount integer,
        @Years integer,
        @SeqName nvarchar(20);

    -- Count rows and distinct lodgement years in the insert set
    SELECT 
        @RowCount = COUNT(*),
        @Years = COUNT(DISTINCT(YEAR(INS.LodgementDate))),
        @SeqName = N'PermitIDs for ' + 
            CONVERT(nchar(4), MIN(YEAR(INS.LodgementDate)))
    FROM Inserted AS INS;

    -- Check for multiple lodgement years (not implemented)
    IF @Years > 1
    BEGIN
        RAISERROR('Multiple LodgementDate years are not supported.', 16, 1);
        IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION;
        RETURN;        
    END;

    -- Allocate the range of sequence numbers we will need
    EXECUTE @rc = dbo.Allocate_TSQL
        @SequenceName = @SeqName,
        @RangeSize = @RowCount,
        @FirstAllocated = @FirstAllocated OUTPUT;

    IF @rc <> 0
    BEGIN
        RAISERROR('Sequence allocation failed.', 16, 1);
        IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION;
        RETURN;
    END;

    -- Assign ImportPermitNo values using the sequence numbers allocated
    WITH Sequenced AS
    (
        SELECT
            IP.ImportPermitNo,
            IP.LodgementDate,
            Seq = 
                @FirstAllocated - 1 + 
                ROW_NUMBER() OVER (
                    ORDER BY IP.LodgementDate ASC)
        FROM Inserted AS INS
        JOIN dbo.ImportPermits AS IP
            ON IP.ImportPermitID = INS.ImportPermitID
    )
    UPDATE Sequenced
    SET Sequenced.ImportPermitNo = 
        N'IP' + 
        CONVERT(nchar(4), YEAR(Sequenced.LodgementDate)) +
        RIGHT(N'000000' + CONVERT(nvarchar(11), Sequenced.Seq), 6);
END;

簡潔にするために、トリガーは単一の年からのみ行を挿入することに限定されていますが、ロジックを拡張することは難しくありません。

デモ:db <> fiddle

6
Paul White 9

このテーブルの使用頻度に応じて、トランザクションを開始する、年の最終日に23:59:50(またはその頃)にスケジュールされるSQL Serverエージェントジョブを設定してみてください。値を再シードしますIDの現在の年への固定 '0001'-2016年の場合、IDは20160001であり、2017年の場合、IDは20170001です-(トランザクションを使用すると、開いているトランザクションにより、他のユーザーがテーブルに効果的にロックアウトされます)真夜中(現在の年が変更されることがわかっている場合)を待ってから、トランザクションをコミットしますか?何かのようなもの

begin transaction
declare @cmd nvarchar(500) = N'DBCC CHECKIDENT(''dbo.tblPermits'', RESEED,' + convert(varchar(4),year(sysdatetime())) + '0001)'
exec (@cmd)
waitfor time '00:00:00'
commit
1
Scott Hodgin