SQL Server 2008: sequenza che si riavvia quotidianamente


8

Devo aggiungere un trigger che dovrebbe aggiornare una colonna usando le seguenti stringhe di formato: <current_date>_<per_day_incremental_id>ad es 2015-10-01_36. Gli ID devono essere incrementali e gli spazi vuoti sono consentiti.

Il mio approccio è piuttosto ingenuo: creare una tabella con la data corrente e il valore della sequenza corrente e mantenere un singolo record al suo interno:

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

Domande:

  • Ci sono delle insidie ​​per la mia soluzione? ad es. il codice all'interno del trigger non verrà eseguito all'interno di una transazione, fornendo quindi valori errati.
  • Mi sto perdendo una soluzione migliore?

Il codice all'interno del trigger certamente verrà eseguito nel contesto della transazione che avvia la modifica nella tabella sottostante.
Max Vernon,

1
Perché è necessario memorizzare questi valori concatenati insieme? Questi valori devono davvero essere permanenti anziché determinati in fase di esecuzione?
Aaron Bertrand

1
Se gli spazi vuoti negli ID sono consentiti, puoi avere un singolo semplice IDENTITYche non viene ripristinato ogni giorno e aggiungerlo alla data corrente. Ogni nuovo giorno sembrerà avere un "divario" sempre più grande, ma il divario è permesso, non è vero? È uno scherzo, ovviamente, ma sottolinea che è necessario aver omesso alcuni requisiti.
Vladimir Baranov,

Risposte:


4

Un potenziale metodo per farlo sarebbe (vedi il metodo migliore, alla fine):

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;

I risultati:

inserisci qui la descrizione dell'immagine


Detto questo, vorrei chiedere perché non semplicemente mantenere due colonne separate che potrebbero essere concatenate nel livello di presentazione:

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;

I risultati:

inserisci qui la descrizione dell'immagine

Il metodo sopra è molto più capace di ridimensionare bene e offre flessibilità nella presentazione del numero di sequenza leggibile dall'uomo.


4

È possibile semplificare l'aggiornamento della parte della DailySequencetabella. Invece di questo:

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

potresti usare questo:

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

La @retvariabile verrebbe quindi inizializzata nell'istruzione UPDATE con il valore memorizzato in sequence.

In alternativa, puoi anche eliminare la set @nowDate = getdate();dichiarazione riscrivendo l'UPDATE in questo modo:

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

o, forse, anche così:

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

In questo modo l'istruzione UPDATE avrebbe inizializzato sia @nowDatee @ret. La @tempDatevariabile non sarebbe necessaria con nessuna delle due opzioni.

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.