SQL Server 2008:毎日再起動するシーケンス


8

:私は、次の形式の文字列を使用して列を更新する必要があり、トリガーを追加する必要があり<current_date>_<per_day_incremental_id>、例えば2015-10-01_36。IDは増分である必要があり、ギャップは許容されます。

私のアプローチはかなり素朴です。現在の日付と現在のシーケンス値でテーブルを作成し、その中に単一のレコードを保持します。

create table DailySequence
(
    date date,
    sequence int
)

insert into DailySequence values (getdate(), 1);

CREATE TRIGGER MakeHumanReadableId ON dbo.AuditMeasures
FOR INSERT
AS
    DECLARE @ret int;
    DECLARE @tempDate date;
    DECLARE @nowDate date;

    SET @nowDate = getdate();

    SELECT @ret = t.sequence, @tempDate = t.date from DailySequence as t;

    IF @nowDate = @tempDate
    BEGIN
        SET @ret = @ret + 1;

        UPDATE DailySequence 
        SET sequence = @ret;
    END
    ELSE
    BEGIN
        SET @ret = 0;

        UPDATE DailySequence 
        SET sequence = @ret, date = @nowDate;
    END

    UPDATE AuditMeasures
    SET [HumanReadableId] = CAST(@nowdate AS VARCHAR(10)) + '_' + CAST(@ret AS VARCHAR(10));
    FROM inserted 
    INNER JOIN AuditMeasures On inserted.id = AuditMeasures.id
GO

質問:

  • 私のソリューションに落とし穴はありますか?たとえば、トリガー内のコードはトランザクション内で実行されないため、不正な値が返されます。
  • より良い解決策がありませんか?

トリガー内のコードは確かだろう基礎となる表の変更を開始し、トランザクションのコンテキスト内で実行されます。
Max Vernon

1
これらの値を連結して保存する必要があるのはなぜですか?これらの値は、実行時に決定されるのではなく、永続的である必要がありますか?
Aaron Bertrand

1
IDのギャップが許容される場合は、毎日リセットされない単純なシングルIDENTITYを使用して、現在の日付に追加できます。新しい日はだんだん「ギャップ」が大きくなるように見えますが、ギャップは許容されますね。もちろん冗談ですが、いくつかの要件を省略している必要があることを強調しています。
Vladimir Baranov、2015年

回答:


4

これを実行する1つの潜在的な方法は次のとおりです(最後に、より良い方法を参照してください)。

USE tempdb;

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
    */

    DECLARE @Retry int;
    DECLARE @EN int, @ES int, @ET int;
    SET @Retry = 5;
    DECLARE @NewID int;
    SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
    SET NOCOUNT ON;
    WHILE @Retry > 0
    BEGIN
        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

CREATE TABLE dbo.HumanReadableSequence
(
    HumanReadableSequence_ID VARCHAR(20) NOT NULL
        CONSTRAINT PK_HumanReadableSequence
        PRIMARY KEY CLUSTERED
    , SomeData VARCHAR(386) NOT NULL
);

GO
CREATE PROCEDURE dbo.HumanReadableSequence_Insert
(
    @SomeData VARCHAR(386)
)
AS
BEGIN
    SET NOCOUNT ON;
    DECLARE @NextID INT;
    DECLARE @Today VARCHAR(20);
    DECLARE @t TABLE 
    (
        ID INT NOT NULL
    );
    SET @Today = (CONVERT(VARCHAR(20), GETDATE(), 101))

    INSERT INTO @t (ID)
    EXEC dbo.GetNextID @IDName = @Today;

    INSERT INTO dbo.HumanReadableSequence (HumanReadableSequence_ID, SomeData)
    SELECT (@Today + '_' + CONVERT(VARCHAR(20), ID, 0))
        , @SomeData
    FROM @t;
END
GO

EXEC dbo.HumanReadableSequence_Insert N'this is a test';

SELECT *
FROM dbo.HumanReadableSequence;

結果:

ここに画像の説明を入力してください


以上のことをすべて説明した後、プレゼンテーションレイヤーに連結できる2つの個別の列を単に維持しないのはなぜでしょうか。

CREATE TABLE dbo.HumanReadableSequence
(
    CreateDate DATETIME NOT NULL
        CONSTRAINT DF_HumanReadableSequence_CreateDate
        DEFAULT (DATEADD(DAY, 0, DATEDIFF(DAY, 0, GETDATE())))
    , HumanReadableSequence_ID INT NOT NULL
    , SomeData VARCHAR(386) NOT NULL
    , CONSTRAINT PK_HumanReadableSequence
        PRIMARY KEY CLUSTERED
        (CreateDate, HumanReadableSequence_ID)
);

DECLARE @ID INT;
DECLARE @t TABLE 
(
    ID INT NOT NULL
);
DECLARE @Today VARCHAR(20);
SET @Today = (CONVERT(VARCHAR(20), GETDATE(), 101))

INSERT INTO @t (ID)
EXEC dbo.GetNextID @IDName = @Today;

SELECT @ID = t.ID
FROM @t t;

INSERT INTO dbo.HumanReadableSequence (SomeData, HumanReadableSequence_ID)
VALUES ('This is a test', @ID);

SELECT HumanReadableSequenceValue = 
        REPLACE(CONVERT(VARCHAR(20), hrs.CreateDate, 101) 
        + '_' 
        + CONVERT(VARCHAR(20), hrs.HumanReadableSequence_ID, 0), '/', '-')
    , SomeData
FROM dbo.HumanReadableSequence hrs;

結果:

ここに画像の説明を入力してください

上記の方法は、はるかに優れたスケーリングが可能で、人間が読み取れるシーケンス番号の表示に柔軟性を提供します。


4

DailySequenceテーブルを更新する部分を簡略化できます。これの代わりに:

select @ret = t.sequence, @tempDate = t.date from DailySequence as t;
if @nowDate = @tempDate
begin
    set @ret = @ret + 1;
    update DailySequence set sequence = @ret;
end
else
begin
    set @ret = 0;
    update DailySequence set sequence = @ret, date = @nowDate;
end

あなたはこれを使うことができます:

UPDATE
  dbo.DailySequence
SET
  @ret = sequence = CASE date WHEN @nowDate THEN sequence + 1 ELSE 0 END,
  date = @nowDate
;

したがって、@ret変数はUPDATEステートメントで初期化され、値がに格納されsequenceます。

または、次のようにUPDATEを書き換えて、ステートメントを削除することできますset @nowDate = getdate();

UPDATE
  dbo.DailySequence
SET
  @ret     = sequence = CASE date WHEN CAST(GETDATE() AS date) THEN sequence + 1 ELSE 0 END,
  @nowDate = date     = GETDATE()
;

または、おそらく、このようにさえ:

UPDATE
  dbo.DailySequence
SET
  @ret     = sequence = CASE date WHEN x.Today THEN sequence + 1 ELSE 0 END,
  @nowDate = date     = x.Today
FROM
  (SELECT CAST(GETDATE() AS date)) AS x (Today)
;

このようにして、UPDATEステートメントはとの両方@nowDateを初期化し@retます。@tempDate変数は、いずれかのオプションで必要とされないであろう。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.