条件付きの一意の識別子フィールド


8

私は稼働していないデータベースを持っているので、メインテーブルはCustodyDetailsで、このテーブルにはID int IDENTITY(1,1) PRIMARY KEY列があり、他のテーブルで参照されていない別の一意の識別子を追加する方法を探しています。アカウントの内容は、正確にはIDキーではありません。

ただし、この新しいID列にはいくつかの具体的な詳細があり、ここから問題が始まります。フォーマットは次のとおりです。XX/YYどこXXはリセットが/すべての新しい年に再起動することを自動incrementable値であり、YYは現在の年の最後の2桁ですSELECT RIGHT(YEAR(GETDATE()), 2)

したがって、たとえば、レコードから開始日追加されたふりをすることができます1 28/12/2015終了2016年3月1日には、カラムは次のようになります。

ID    ID2     DATE_ADDED
1     1/15    2015-12-28
2     2/15    2015-12-29
3     3/15    2015-12-30
4     4/15    2015-12-31
5     1/16    2016-01-01
6     2/16    2016-01-02
7     3/16    2016-01-03

フロントエンドを使用してコンポジットID(例ではID2)を解析し、最後の2桁を取得して現在の年の最後の2桁と比較し、新しい相関を開始するかどうかを決定することを考えました。もちろん、データベース側でそれをすべて実行できることは素晴らしいことです。

編集1:ところで、私は人々が並列のIDキーを格納するためだけに別々のテーブルを使用しているのを見たので、1つのテーブルのIDキーは2番目のテーブルのセカンダリキーになります、これは少しおかしな話に聞こえますが、おそらくこれはそのような実装が行われるケースですか?

編集2:この追加の IDは、すべてのファイル/レコードにラベルを付けるレガシードキュメント参照です。私はそれをメインIDの特別なエイリアスと考えることができると思います。

このデータベースが毎年処理するレコードの数は、過去20年間で100を超えていません。もちろん、99を超えるとフィールドでできることはほとんどありません(実際には、非常に非常に)。追加の桁で続行すると、フロントエンド/手順は99を超えることができるため、状況が変わることはありません。

もちろん、最初に言及しなかったこれらの詳細の一部は、特定のニーズに合わせてソリューションの可能性を絞り込むだけなので、問題の範囲をより広く保つことを試みました。


これはどのバージョンのSQL Serverですか?
Max Vernon

どこにも参照として使用しない場合、なぜこれをテーブルに格納する必要があるのですか?なぜそれが計算列ではないのですか(必要に応じて、クエリで永続化または計算されます)。年間100行を超える場合はどうなりますか?
ypercubeᵀᴹ

1
ID= 5、6、及び7は、DATE_ADDEDがなければならない2016-01-01 というように?
キンシャー

@Kinはそのように見えます。サンプルを修正しました。
ypercubeᵀᴹ

修正してくれてありがとう、はい、彼らは2016年のレコードで、私が現在使用しているSQL Server 2005です。@YperSillyCubeᵀᴹそれは主により良い解決策を見つけることの問題なので、本当にどんな提案でも認められます。
Nelz

回答:


6

キーテーブルを使用して、2番目のID列の増分部分を格納できます。このソリューションは、クライアント側のコードに依存せず、自動的に複数年認識されます。とき@DateAddedのパラメータは、以前に使用されていない年に渡し、それが自動的に値の新しいセットを使用して開始されID2、その年に基づいて、コラム。その結果、procが前の年の行を挿入するために使用される場合、それらの行は、増分の「正しい」値で挿入されます。GetNextID()PROCを更新しようとしたときに5つのシーケンシャルデッドロックが発生した場合にのみ、呼び出し元にエラーを渡して、優雅にデッドロックの可能性を処理するために適していtblIDsテーブルを。

使用する新しい値を返すストアドプロシージャと共に、現在使用されているID値を含む年に1行を格納するテーブルを作成します。

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

    */
    SET NOCOUNT ON;

    DECLARE @Retry int;
    DECLARE @EN int, @ES int, @ET int;
    SET @Retry = 5;
    DECLARE @NewID int;
    WHILE @Retry > 0
    BEGIN
        SET @NewID = NULL;
        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.Cond
(
    CondID INT NOT NULL
        CONSTRAINT PK_Cond
        PRIMARY KEY CLUSTERED
        IDENTITY(1,1)
    , CondID2 VARCHAR(30) NOT NULL
    , Date_Added DATE NOT NULL
);

GO
CREATE PROCEDURE dbo.InsertCond
(
    @DateAdded DATE
)
AS
BEGIN
    DECLARE @NextID INT;
    DECLARE @Year INT;
    DECLARE @IDName NVARCHAR(255);
    SET @Year = DATEPART(YEAR, @DateAdded);
    DECLARE @Res TABLE
    (
        NextID INT NOT NULL
    );
    SET @IDName = 'Cond_' + CONVERT(VARCHAR(30), @Year, 0);
    INSERT INTO @Res (NextID)
    EXEC dbo.GetNextID @IDName;

    INSERT INTO dbo.Cond (CondID2, Date_Added)
    SELECT CONVERT(VARCHAR(30), NextID) + '/' + 
        SUBSTRING(CONVERT(VARCHAR(30), @Year), 3, 2), @DateAdded
    FROM @Res;
END
GO

いくつかのサンプルデータを挿入します。

EXEC dbo.InsertCond @DateAdded = '2015-12-30';
EXEC dbo.InsertCond @DateAdded = '2015-12-31';
EXEC dbo.InsertCond @DateAdded = '2016-01-01';
EXEC dbo.InsertCond @DateAdded = '2016-01-02';

両方のテーブルを表示:

SELECT *
FROM dbo.Cond;

SELECT *
FROM dbo.tblIDs;

結果:

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

キーテーブルとストアドプロシージャは、この質問に由来します。


トランザクション分離レベルを設定していますが、トランザクションを明示的に開いていません。また、2つの同時セッションが同じ(IDName, LastID)行を挿入しようとした場合、デッドロックになるか、トランザクションの1つがPKに違反することになりますか?後者の場合、おそらくそのトランザクションに別の機会を与えることは理にかなっています(その結果、最終的にIDが2になる)。
Andriy M

そしてもうひとつは、私はおそらく設定します@NewIDループの先頭にnullに明示的に:試みが行を挿入することトランザクションがデッドロックの対象となった場合、それは次の反復で行を挿入しようとはしません、ので、@NewID意志が既に持っています1に設定されています(これはNULLではないため、INSERTブランチは省略されます)。
Andriy M

実際には、トランザクション分離レベルを設定する必要はまったくありません。削除します。tblIDsテーブルは単一のアトミック操作によって更新されるため、2つの同時セッションがテーブルに同じ値を挿入する方法はわかりません。「風変わりな」アップデート。
Max Vernon

@NewID = NULLループの開始時に設定することは悪い考えではありません。
Max Vernon

理論的には、新年の最初のアクションはデッドロックの影響を受けやすいかもしれません。
Max Vernon、
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.