SQL Server 2008: sequência que reinicia diariamente


8

Eu tenho que adicionar um gatilho que deve atualizar uma coluna usando as seguintes seqüências de formato:, <current_date>_<per_day_incremental_id>por exemplo 2015-10-01_36. Os IDs devem ser incrementais e as lacunas são permitidas.

Minha abordagem é bastante ingênua: faça uma tabela com a data atual e o valor atual da sequência e mantenha um único registro nela:

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

Questões:

  • Existem armadilhas para a minha solução? por exemplo, o código dentro do gatilho não será executado dentro de uma transação, fornecendo valores incorretos.
  • Estou perdendo uma solução melhor?

O código dentro do gatilho certamente será executado dentro do contexto da transação que inicia a alteração na tabela subjacente.
— Max Vernon

11
Por que você precisa armazenar esses valores concatenados juntos? Esses valores realmente precisam ser permanentes em vez de determinados em tempo de execução?
— Aaron Bertrand

11
Se as lacunas nos IDs forem permitidas, você poderá criar uma única simples IDENTITYque não seja redefinida todos os dias e anexá-la à data atual. Cada novo dia parecerá ter cada vez mais espaço, mas o espaço é permitido, não é? É uma piada, é claro, mas destaca que você deve ter omitido alguns requisitos.
— Vladimir Baranov

Respostas:


4

Um método potencial de fazer isso seria (veja o melhor método, no final):

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;

Os resultados:

insira a descrição da imagem aqui


Dito tudo isso, eu perguntaria por que não simplesmente manter duas colunas separadas que poderiam ser concatenadas na camada de apresentação:

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;

Os resultados:

insira a descrição da imagem aqui

O método acima é muito mais capaz de escalar bem e oferece flexibilidade na apresentação do número de sequência legível por humanos.


4

Você pode simplificar a parte que atualiza a DailySequencetabela. Em vez disso:

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

você poderia usar isso:

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

A @retvariável seria assim inicializado na declaração UPDATE com o valor armazenado em sequence.

Como alternativa, você também pode se livrar da set @nowDate = getdate();declaração reescrevendo UPDATE da seguinte maneira:

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

ou, talvez, mesmo assim:

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

Dessa forma, a instrução UPDATE inicializaria ambos @nowDatee @ret. A @tempDatevariável não seria necessária com nenhuma das opções.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.