Champ d'identifiant unique avec conditionnel

8

J'ai une base de données qui n'est pas en production, donc la table principale étant CustodyDetails, cette table a une ID int IDENTITY(1,1) PRIMARY KEYcolonne et je cherche un moyen d'ajouter un autre identifiant unique qui n'est référencé dans aucune autre table, je pense qu'en prenant cela dans compte le contenu de la colonne ne serait pas exactement une clé d'identité.

Cette nouvelle colonne d'identité contient cependant quelques détails spécifiques, et voici où commence mon problème. Le format est le suivant: XX/YYXX est une valeur auto-incrémentable qui se réinitialise / redémarre chaque nouvelle année et YY est les 2 derniers chiffres de l'année en cours SELECT RIGHT(YEAR(GETDATE()), 2).

Ainsi, par exemple, supposons qu'un enregistrement est ajouté un jour à partir du 28/12/2015 se terminant le 03/01/2016 , la colonne ressemblerait à:

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

J'ai pensé à utiliser l'interface pour analyser l'ID composite (ID2 dans l'exemple), obtenir les 2 derniers chiffres et comparer avec les 2 derniers chiffres de l'année en cours, puis décider de commencer ou non une nouvelle corrélation. Bien sûr, ce serait formidable de pouvoir tout faire du côté de la base de données.

EDIT 1: btw, j'ai également vu des gens utiliser des tables distinctes juste pour stocker des clés d'identité parallèles, donc une clé d'identité de table devient une deuxième clé secondaire de table, cela semble un peu douteux mais c'est peut-être le cas si une telle implémentation est en place?

EDIT 2: Cet ID supplémentaire est une référence de document héritée qui étiquette chaque fichier / enregistrement. Je suppose que l'on pourrait le considérer comme un alias spécial pour l'ID principal.

Le nombre d'enregistrements que cette base de données gère chaque année n'a pas été parmi les 100 au cours des 20 dernières années et il est hautement (vraiment, extrêmement fortement) improbable que cela le fasse, bien sûr, s'il dépasse 99, le champ pourra continuez avec le chiffre supplémentaire et le frontend / procédure pourra dépasser 99, donc ce n'est pas comme si cela changeait les choses.

Bien sûr, certains de ces détails que je n'ai pas mentionnés au début, car ils ne feraient que restreindre les possibilités de solution pour répondre à mes besoins spécifiques, ont essayé de garder la gamme de problèmes plus large.

Nelz
la source
De quelle version de SQL Server s'agit-il?
Max Vernon
Pourquoi cela doit-il être stocké dans le tableau, s'il ne doit être utilisé comme référence nulle part? Pourquoi ne peut-il pas s'agir d'une colonne calculée (persistante ou calculée dans une requête, si nécessaire)? Que devrait-il se passer si vous avez plus de 100 lignes en un an?
ypercubeᵀᴹ
1
Avec ID= 5, 6 et 7, le DATE_ADDED devrait être 2016-01-01 et ainsi de suite?
Kin Shah
@Kin y ressemble. J'ai corrigé l'échantillon.
ypercubeᵀᴹ
Merci pour la correction, oui, ils étaient des enregistrements de 2016 et son SQL Server 2005 que j'utilise maintenant. @ YperSillyCubeᵀᴹ Son principalement une question de trouver une meilleure solution, donc vraiment toute suggestion serait appréciée.
Nelz

Réponses:

6

Vous pouvez utiliser une table de clés pour stocker la partie incrémentielle de votre deuxième colonne ID. Cette solution ne repose sur aucun code côté client et prend automatiquement en compte plusieurs années; lorsque le @DateAddedparamètre passe dans une année précédemment inutilisée, il commencera automatiquement à utiliser un nouvel ensemble de valeurs pour la ID2colonne, basé sur cette année. Si le proc est par conséquent utilisé pour insérer des lignes des années précédentes, ces lignes seront insérées avec des valeurs "correctes" pour l'incrément. Le GetNextID()proc est conçu pour gérer les blocages possibles avec élégance, ne transmettant une erreur à l'appelant que si 5 blocages séquentiels se produisent lors de la tentative de mise à jour de la tblIDstable.

Créez une table pour stocker une ligne par an contenant la valeur d'ID actuellement utilisée, ainsi qu'une procédure stockée pour renvoyer la nouvelle valeur à utiliser:

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

Votre table, avec un proc pour y insérer des lignes:

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

Insérez quelques exemples de données:

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

Afficher les deux tableaux:

SELECT *
FROM dbo.Cond;

SELECT *
FROM dbo.tblIDs;

Résultats:

entrez la description de l'image ici

La table des clés et le proc stocké proviennent de cette question.

Max Vernon
la source
Vous définissez le niveau d'isolement des transactions mais n'ouvrez pas explicitement une transaction. De plus, si deux sessions simultanées tentaient d'insérer la même (IDName, LastID)ligne, cela entraînerait-il un blocage ou l'une des transactions violant le PK? Dans ce dernier cas, il serait peut-être judicieux de donner à cette transaction une autre chance (afin qu'elle obtienne finalement l'ID de 2).
Andriy M
Et une autre chose, je définirais probablement @NewIDexplicitement la valeur null au début de la boucle: si la transaction qui tente d'insérer une ligne devient une victime de blocage, elle n'essaiera pas d'insérer une ligne à la prochaine itération, car @NewIDaura déjà a été défini sur 1 (ce qui n'est pas NULL, et donc la branche INSERT sera omise).
Andriy M
En fait, le niveau d'isolement des transactions n'a pas du tout besoin d'être défini; Je vais l'enlever. Je ne vois pas comment deux sessions simultanées pourraient jamais insérer la même valeur dans la tblIDstable puisque cette table est mise à jour par une seule opération atomique; la mise à jour "décalée".
Max Vernon
Pas une mauvaise idée sur la mise @NewID = NULLen début de boucle.
Max Vernon
Je suppose que théoriquement la toute première action pour une nouvelle année pourrait être bloquée.
Max Vernon