SQL Server 2008: séquence qui redémarre quotidiennement


8

Je dois ajouter un déclencheur qui devrait mettre à jour une colonne en utilisant les chaînes de format suivantes:, <current_date>_<per_day_incremental_id>par exemple 2015-10-01_36. Les identifiants doivent être incrémentiels et les écarts sont autorisés.

Mon approche est plutôt naïve: créer un tableau avec la date actuelle et la valeur de séquence actuelle et y conserver un seul enregistrement:

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

Des questions:

  • Y a-t-il des écueils pour ma solution? Par exemple, le code à l'intérieur du déclencheur ne s'exécutera pas à l'intérieur d'une transaction, donnant ainsi des valeurs incorrectes.
  • Suis-je en train de manquer une meilleure solution?

Code à l' intérieur de la gâchette certainement se lancer dans le cadre de la transaction qui initie le changement de la table sous - jacente.
Max Vernon

1
Pourquoi avez-vous besoin de stocker ces valeurs concaténées ensemble? Ces valeurs doivent-elles vraiment être permanentes plutôt que déterminées lors de l'exécution?
Aaron Bertrand

1
Si les écarts dans les ID sont autorisés, vous pouvez avoir un simple simple IDENTITYqui n'est pas réinitialisé tous les jours et l'ajouter à la date actuelle. Chaque nouveau jour aura l'air d'avoir un "écart" de plus en plus grand, mais l'écart est autorisé, n'est-ce pas? C'est une plaisanterie, bien sûr, mais cela souligne que vous devez avoir omis certaines exigences.
Vladimir Baranov du

Réponses:


4

Une méthode possible pour ce faire serait (voir la meilleure méthode, à la fin):

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;

Les resultats:

entrez la description de l'image ici


Cela dit, je demanderais pourquoi ne pas simplement conserver deux colonnes distinctes qui pourraient être concaténées dans la couche de présentation:

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;

Les resultats:

entrez la description de l'image ici

La méthode ci-dessus est beaucoup plus capable de bien évoluer et offre une flexibilité dans la présentation du numéro de séquence lisible par l'homme.


4

Vous pouvez simplifier la mise à jour de la DailySequencetable. Au lieu de cela:

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

vous pouvez utiliser ceci:

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

La @retvariable serait donc initialisée dans l'instruction UPDATE avec la valeur stockée dans sequence.

Alternativement, vous pouvez également vous débarrasser de l' set @nowDate = getdate();instruction en réécrivant la MISE À JOUR comme ceci:

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

ou peut-être même comme ceci:

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

De cette façon, l'instruction UPDATE initialiserait à la fois @nowDateet @ret. La @tempDatevariable ne serait pas nécessaire avec l'une ou l'autre option.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.