Campo identificativo univoco con un condizionale


8

Ho un database che non è in produzione, quindi la tabella principale è CustodyDetails, questa tabella ha una ID int IDENTITY(1,1) PRIMARY KEYcolonna e sto cercando un modo per aggiungere un altro identificatore univoco a cui non fa riferimento in nessun'altra tabella, penserei prendendo questo in conto il contenuto della colonna non sarebbe esattamente una chiave di identità.

Questa nuova colonna di identità ha però alcuni dettagli specifici, ed ecco dove inizia il mio problema. Il formato è il seguente: XX/YYdove XX è un valore auto-incrementabile che reimposta / riavvia ogni anno nuovo e YY è le ultime 2 cifre dell'anno corrente SELECT RIGHT(YEAR(GETDATE()), 2).

Quindi, ad esempio, facciamo finta che un record venga aggiunto un giorno a partire dal 28/12/2015 fino al 03/01/2016 , la colonna sarebbe simile a:

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

Ho pensato di usare il frontend per analizzare l'ID composito (ID2 nell'esempio) per ottenere le ultime 2 cifre e confrontarle con le ultime 2 cifre dell'anno in corso e quindi decidere se iniziare o meno un nuovo correlativo. Ovviamente sarebbe grandioso poter fare tutto dal lato del database.

EDIT 1: a proposito, ho visto anche persone che usano tabelle separate solo per memorizzare chiavi di identità parallele, quindi una chiave Identity di una tabella diventa una seconda chiave secondaria di tabella, sembra un po 'confusa ma forse è il caso che tale implementazione venga messa in atto?

MODIFICA 2: questo ID aggiuntivo è un riferimento al documento legacy che identifica ogni file / record. Immagino che si possa pensare ad esso come un alias speciale per l'ID principale.

Il numero di record che questo database gestisce annualmente non è stato tra i 100 negli ultimi 20 anni ed è altamente (davvero, estremamente altamente) improbabile che, ovviamente, se supera i 99, il campo sarà in grado di vai avanti con la cifra in più e la procedura / frontend sarà in grado di superare 99, quindi non è come se cambiasse le cose.

Naturalmente alcuni di questi dettagli non ho menzionato all'inizio perché restringerebbero solo le possibilità di soluzione per soddisfare le mie esigenze specifiche, cercando di mantenere il problema più ampio.


Di quale versione di SQL Server si tratta?
Max Vernon,

Perché questo deve essere memorizzato nella tabella, se non deve essere usato come riferimento da nessuna parte? Perché non può essere una colonna calcolata (persistente o calcolata in una query, quando necessario)? Cosa dovrebbe succedere se si hanno più di 100 file in un anno?
ypercubeᵀᴹ

1
Con ID= 5, 6 e 7, DATE_ADDED dovrebbe essere 2016-01-01 e così via?
Kin Shah,

@Kin sembra. Ho corretto il campione.
ypercubeᵀᴹ

Grazie per la correzione, sì, erano le registrazioni del 2016 e il suo SQL Server 2005 che sto usando ora. @ YperSillyCubeᵀᴹ Si tratta principalmente di trovare una soluzione migliore , quindi qualsiasi suggerimento sarebbe apprezzato.
Nelz,

Risposte:


6

È possibile utilizzare una tabella di chiavi per memorizzare la parte incrementale della seconda colonna ID. Questa soluzione non si basa su alcun codice lato client ed è automaticamente consapevole per più anni; quando il @DateAddedparametro passa in un anno inutilizzato, inizierà automaticamente a utilizzare un nuovo set di valori per la ID2colonna, in base a quell'anno. Se il proc viene di conseguenza utilizzato per inserire righe degli anni precedenti, tali righe verranno inserite con valori "corretti" per l'incremento. Il GetNextID()proc è orientato a gestire i possibili deadlock con garbo, passando un errore al chiamante solo se si verificano 5 deadlock sequenziali quando si tenta di aggiornare la tblIDstabella.

Creare una tabella per archiviare una riga all'anno contenente il valore ID attualmente utilizzato, insieme a una procedura memorizzata per restituire il nuovo valore da utilizzare:

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

La tua tabella, insieme a un proc per inserire righe in esso:

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

Inserisci alcuni dati di esempio:

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

Mostra entrambe le tabelle:

SELECT *
FROM dbo.Cond;

SELECT *
FROM dbo.tblIDs;

risultati:

inserisci qui la descrizione dell'immagine

La tabella delle chiavi e il proc memorizzato provengono da questa domanda.


Stai impostando il livello di isolamento della transazione ma non stai aprendo esplicitamente una transazione. Inoltre, se due sessioni simultanee tentassero di inserire la stessa (IDName, LastID)riga, ciò comporterebbe un deadlock o una delle transazioni che violano il PK? Se quest'ultimo, forse avrebbe senso dare a quella transazione un'altra possibilità (in modo che alla fine otterrebbe l'ID di 2).
Andriy M,

E un'altra cosa, probabilmente imposterei @NewIDesplicitamente su null all'inizio del ciclo: se la transazione che tenta di inserire una riga diventa vittima di un deadlock, non tenterà di inserire una riga alla successiva iterazione, perché @NewIDavrà già stato impostato su 1 (che non è NULL, quindi il ramo INSERT verrà omesso).
Andriy M,

In realtà, non è necessario impostare il livello di isolamento della transazione; Lo rimuoverò. Non vedo come due sessioni simultanee possano mai inserire lo stesso valore nella tblIDstabella poiché quella tabella viene aggiornata da una singola operazione atomica; l'aggiornamento "eccentrico".
Max Vernon,

Non è una cattiva idea sull'impostazione @NewID = NULLall'inizio del loop.
Max Vernon,

Suppongo teoricamente che la primissima azione per un nuovo anno potrebbe essere suscettibile di deadlock.
Max Vernon,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.