Bidang pengidentifikasi unik dengan kondisi

8

Saya memiliki database yang tidak dalam produksi, jadi tabel utamanya adalah CustodyDetails, tabel ini memiliki ID int IDENTITY(1,1) PRIMARY KEYkolom dan saya sedang mencari cara menambahkan pengidentifikasi unik yang tidak direferensikan di tabel lain, saya akan berpikir dengan mengambil ini di akun isi kolom tidak akan persis kunci identitas.

Kolom identitas baru ini memiliki beberapa detail spesifik, dan di sinilah masalah saya dimulai. Formatnya adalah sebagai berikut: di XX/YYmana XX adalah nilai tambah otomatis yang mengatur ulang / memulai kembali setiap tahun baru dan YY adalah 2 digit terakhir tahun ini SELECT RIGHT(YEAR(GETDATE()), 2).

Jadi misalnya mari kita berpura-pura satu catatan ditambahkan sehari mulai dari 28/12/2015 yang berakhir pada 03/01/2016 , kolomnya akan terlihat seperti:

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

Saya berpikir untuk menggunakan frontend untuk mengurai ID komposit (ID2 dalam contoh) mendapatkan 2 digit terakhir dan membandingkannya dengan 2 digit terakhir tahun ini dan kemudian memutuskan apakah akan memulai korelatif baru atau tidak. Tentu saja akan luar biasa untuk dapat melakukan semuanya di sisi basis data.

EDIT 1: btw, saya juga melihat orang menggunakan tabel terpisah hanya untuk menyimpan kunci identitas paralel, jadi satu tabel kunci Identitas menjadi kunci kedua tabel sekunder, ini terdengar agak cerdik tapi mungkin ini kasus implementasi seperti itu terjadi?

EDIT 2: ID tambahan ini adalah referensi dokumen lawas yang memberi label setiap file / catatan. Saya kira orang bisa menganggapnya sebagai alias khusus untuk ID utama.

Jumlah catatan yang ditangani oleh database ini setiap tahun belum mencapai 100 dalam 20 tahun terakhir dan sangat (sangat, sangat sangat) mustahil untuk dilakukan, tentu saja jika melebihi 99, maka bidang tersebut akan dapat Lanjutkan dengan digit tambahan dan frontend / prosedur akan dapat melampaui 99, jadi itu tidak seperti itu mengubah hal-hal.

Tentu saja beberapa perincian ini tidak saya sebutkan di awal karena mereka hanya akan mempersempit kemungkinan solusi untuk memenuhi kebutuhan spesifik saya, mencoba untuk menjaga jangkauan masalah yang lebih luas.

Nelz
sumber
Tentang apa versi SQL Server ini?
Max Vernon
Mengapa ini perlu disimpan dalam tabel, jika tidak digunakan sebagai referensi di mana saja? Mengapa itu tidak bisa berupa kolom yang dihitung (bertahan atau dihitung dalam kueri, bila diperlukan)? Apa yang harus terjadi jika Anda memiliki lebih dari 100 baris dalam setahun?
ypercubeᵀᴹ
1
Dengan ID= 5, 6, dan 7, DATE_ADDED seharusnya 2016-01-01 dan seterusnya?
Kin Shah
@ Kin terlihat seperti itu. Saya mengoreksi sampel.
ypercubeᵀᴹ
Terima kasih atas koreksinya, ya, mereka 2016 recs, dan SQL Server 2005 saya gunakan sekarang. @ YperSillyCubeᵀᴹ Ini terutama masalah menemukan solusi yang lebih baik , jadi benar-benar ada saran yang akan dinilai.
Nelz

Jawaban:

6

Anda bisa menggunakan tabel kunci untuk menyimpan bagian incrementing dari kolom ID kedua Anda. Solusi ini tidak bergantung pada kode sisi klien, dan secara otomatis multi-tahun sadar; ketika @DateAddedparameter melewati pada tahun yang sebelumnya tidak digunakan, itu akan secara otomatis mulai menggunakan set nilai baru untuk ID2kolom, berdasarkan tahun itu. Jika proc secara konsekuen digunakan untuk menyisipkan baris dari tahun-tahun sebelumnya, baris-baris itu akan disisipkan dengan nilai "benar" untuk kenaikan. The GetNextID()proc diarahkan untuk menangani kemungkinan deadlock anggun, hanya melewati kesalahan ke pemanggil jika 5 deadlock berurutan terjadi ketika mencoba untuk memperbarui tblIDstabel.

Buat tabel untuk menyimpan satu baris per tahun yang berisi nilai ID yang saat ini digunakan, bersama dengan prosedur tersimpan untuk mengembalikan nilai baru untuk digunakan:

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

Meja Anda, bersama dengan proc untuk menyisipkan baris ke dalamnya:

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

Masukkan beberapa data sampel:

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';

Tampilkan kedua tabel:

SELECT *
FROM dbo.Cond;

SELECT *
FROM dbo.tblIDs;

Hasil:

masukkan deskripsi gambar di sini

Tabel kunci dan proc yang disimpan berasal dari pertanyaan ini .

Max Vernon
sumber
Anda menetapkan tingkat isolasi transaksi tetapi tidak secara eksplisit membuka transaksi. Juga, jika dua sesi bersamaan mencoba untuk memasukkan (IDName, LastID)baris yang sama , apakah itu akan menghasilkan jalan buntu atau salah satu transaksi melanggar PK? Jika yang terakhir, mungkin masuk akal untuk memberikan transaksi itu kesempatan lain (sehingga akhirnya akan mendapatkan ID 2).
Andriy M
Dan satu hal lagi, saya mungkin akan menetapkan @NewIDsecara eksplisit ke nol pada awal loop: jika transaksi yang mencoba memasukkan baris menjadi korban kebuntuan, itu tidak akan mencoba untuk memasukkan baris pada iterasi berikutnya, karena @NewIDsudah akan memiliki telah diatur ke 1 (yang bukan NULL, sehingga cabang INSERT akan dihilangkan).
Andriy M
Sebenarnya, level isolasi transaksi tidak perlu diatur sama sekali; Saya akan menghapusnya. Saya tidak melihat bagaimana dua sesi bersamaan bisa memasukkan nilai yang sama ke dalam tblIDstabel karena tabel itu diperbarui oleh operasi atom tunggal; pembaruan "aneh".
Max Vernon
Bukan ide buruk tentang pengaturan @NewID = NULLpada awal loop.
Max Vernon
Saya kira secara teoritis tindakan pertama untuk tahun baru mungkin rentan mengalami kebuntuan.
Max Vernon