Eindeutiges Bezeichnerfeld mit einer Bedingung

8

Ich habe eine Datenbank, die nicht in Produktion ist. Die Haupttabelle ist CustodyDetails. Diese Tabelle enthält eine ID int IDENTITY(1,1) PRIMARY KEYSpalte. Ich suche nach einer Möglichkeit, eine weitere eindeutige Kennung hinzuzufügen, auf die in keiner anderen Tabelle verwiesen wird Konto Der Inhalt der Spalte wäre nicht genau ein Identitätsschlüssel.

Diese neue Identitätsspalte enthält jedoch einige spezifische Details, und hier beginnt mein Problem. Das Format lautet wie folgt: XX/YYDabei ist XX ein automatisch inkrementierbarer Wert, der jedes neue Jahr zurückgesetzt / neu gestartet wird, und YY die letzten beiden Ziffern des aktuellen Jahres SELECT RIGHT(YEAR(GETDATE()), 2).

So zum Beispiel kann pretend einen Datensatz pro Tag beginnend hinzugefügt werden vom 28/12/2015 enden 2016.03.01 , die Spalte aussehen würde:

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

Ich dachte daran, das Frontend zu verwenden, um die zusammengesetzte ID zu analysieren (ID2 im Beispiel), die letzten 2 Ziffern abzurufen und mit den letzten 2 Ziffern des aktuellen Jahres zu vergleichen und dann zu entscheiden, ob ein neues Korrelativ gestartet werden soll oder nicht. Natürlich wäre es großartig, alles auf der Datenbankseite erledigen zu können.

EDIT 1: Übrigens habe ich auch Leute gesehen, die separate Tabellen nur zum Speichern paralleler Identitätsschlüssel verwenden. Ein Tabellenidentitätsschlüssel wird also zu einem zweiten Tabellensekundärschlüssel. Das klingt etwas zwielichtig, aber vielleicht ist dies der Fall, wenn eine solche Implementierung erfolgt?

BEARBEITEN 2: Diese zusätzliche ID ist eine Legacy-Dokumentreferenz, die jede Datei / jeden Datensatz kennzeichnet. Ich denke, man könnte es sich als speziellen Alias ​​für die Haupt-ID vorstellen.

Die Anzahl der Datensätze, die diese Datenbank jährlich verarbeitet, war in den letzten 20 Jahren nicht von den 100 und ist höchst (wirklich, extrem hoch) unwahrscheinlich, dass dies natürlich der Fall sein würde, wenn sie über 99 hinausgeht Fahren Sie mit der zusätzlichen Ziffer fort und das Frontend / die Prozedur kann über 99 gehen, so dass es nicht so ist, als würde es Dinge ändern.

Natürlich haben einige dieser Details, die ich am Anfang nicht erwähnt habe, nur die Lösungsmöglichkeiten eingegrenzt, um meinen spezifischen Anforderungen gerecht zu werden, und versucht, den Problembereich breiter zu halten.

Nelz
quelle
Um welche Version von SQL Server handelt es sich?
Max Vernon
Warum muss dies in der Tabelle gespeichert werden, wenn es nirgendwo als Referenz verwendet werden soll? Warum kann es sich nicht um eine berechnete Spalte handeln (bei Bedarf entweder beibehalten oder in einer Abfrage berechnet)? Was sollte passieren, wenn Sie mehr als 100 Zeilen pro Jahr haben?
Ypercubeᵀᴹ
1
Mit ID= 5, 6 und 7 sollte DATE_ADDED sein 2016-01-01 und so weiter?
Kin Shah
@ Kin sieht so aus. Ich habe die Probe korrigiert.
Ypercubeᵀᴹ
Vielen Dank für die Korrektur, ja, es waren 2016 Recs und der SQL Server 2005, den ich jetzt verwende. @ YperSillyCubeᵀᴹ Es geht hauptsächlich darum, eine bessere Lösung zu finden, also wäre wirklich jeder Vorschlag zu begrüßen.
Nelz

Antworten:

6

Sie können eine Schlüsseltabelle verwenden, um den inkrementierenden Teil Ihrer zweiten ID-Spalte zu speichern. Diese Lösung basiert nicht auf clientseitigem Code und ist automatisch mehrjährig verfügbar. Wenn der @DateAddedParameter in einem zuvor nicht verwendeten Jahr übergeben wird, wird automatisch ein neuer Wertesatz für die ID2Spalte verwendet, der auf diesem Jahr basiert. Wenn der Prozess folglich zum Einfügen von Zeilen aus früheren Jahren verwendet wird, werden diese Zeilen mit "korrekten" Werten für das Inkrement eingefügt. Der GetNextID()Prozess ist darauf ausgerichtet, mögliche Deadlocks ordnungsgemäß zu behandeln, und gibt nur dann einen Fehler an den Aufrufer weiter, wenn beim Versuch, die tblIDsTabelle zu aktualisieren, 5 aufeinanderfolgende Deadlocks auftreten .

Erstellen Sie eine Tabelle zum Speichern einer Zeile pro Jahr mit dem aktuell verwendeten ID-Wert sowie eine gespeicherte Prozedur, um den neuen zu verwendenden Wert zurückzugeben:

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

Ihre Tabelle zusammen mit einem Proc zum Einfügen von Zeilen:

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

Fügen Sie einige Beispieldaten ein:

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

Beide Tabellen anzeigen:

SELECT *
FROM dbo.Cond;

SELECT *
FROM dbo.tblIDs;

Ergebnisse:

Geben Sie hier die Bildbeschreibung ein

Die Schlüsseltabelle und der gespeicherte Prozess stammen aus dieser Frage.

Max Vernon
quelle
Sie legen die Transaktionsisolationsstufe fest, öffnen eine Transaktion jedoch nicht explizit. Wenn zwei gleichzeitige Sitzungen versuchen (IDName, LastID)würden, dieselbe Zeile einzufügen , würde dies zu einem Deadlock oder zu einer der Transaktionen führen, die die PK verletzen? In letzterem Fall wäre es möglicherweise sinnvoll, dieser Transaktion eine weitere Chance zu geben (damit sie schließlich die ID 2 erhält).
Andriy M
Und eine andere Sache würde ich wahrscheinlich @NewIDam Anfang der Schleife explizit auf null setzen : Wenn die Transaktion, die versucht, eine Zeile einzufügen, ein Deadlock-Opfer wird, wird sie nicht versuchen, eine Zeile bei der nächsten Iteration einzufügen, da dies @NewIDbereits der Fall ist wurde auf 1 gesetzt (was nicht NULL ist, und daher wird der INSERT-Zweig weggelassen).
Andriy M
Tatsächlich muss die Transaktionsisolationsstufe überhaupt nicht festgelegt werden. Ich werde es entfernen. Ich sehe nicht ein, wie zwei gleichzeitige Sitzungen jemals denselben Wert in die tblIDsTabelle einfügen könnten, da diese Tabelle durch eine einzelne atomare Operation aktualisiert wird. das "skurrile" Update.
Max Vernon
Keine schlechte Idee beim Einstellen @NewID = NULLam Anfang der Schleife.
Max Vernon
Ich nehme an theoretisch die erste Aktion für ein neues Jahr könnte anfällig sein Deadlock.
Max Vernon