Поле уникального идентификатора с условным условием

8

У меня есть база данных, которая не находится в производстве, поэтому основной таблицей является CustodyDetails, в этой таблице есть ID int IDENTITY(1,1) PRIMARY KEYстолбец, и я ищу способ добавления другого уникального идентификатора, на который нет ссылки ни в одной другой таблице. учетная запись содержание столбца не будет точно ключом идентификации.

Этот новый столбец идентификаторов имеет несколько конкретных деталей, и вот здесь начинается моя проблема. Формат выглядит следующим образом: XX/YYгде XX - это автоматически увеличиваемое значение, которое сбрасывает / перезапускает каждый новый год, а YY - последние 2 цифры текущего года SELECT RIGHT(YEAR(GETDATE()), 2).

Так, например , позволяет делать вид , один запись добавляется в день , начиная с 28/12/2015 , заканчивающимися 03/01/2016 , колонок будет выглядеть следующим образом :

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

Я подумал об использовании внешнего интерфейса для анализа составного идентификатора (ID2 в примере), чтобы получить последние 2 цифры и сравнить с последними 2 цифрами текущего года, а затем решить, стоит ли начинать новый коррелятивный анализ. Конечно, было бы здорово иметь возможность делать все это на стороне базы данных.

РЕДАКТИРОВАТЬ 1: Кстати, я также видел людей, использующих отдельные таблицы только для хранения параллельных ключей идентификации, поэтому один ключ идентификации таблицы становится вторым дополнительным ключом таблицы, это звучит немного странно, но, может быть, в таком случае такая реализация имеет место?

РЕДАКТИРОВАТЬ 2: Этот дополнительный идентификатор является ссылкой на устаревший документ, который помечает каждый файл / запись. Я думаю, что это можно назвать специальным псевдонимом для основного идентификатора.

Количество записей, которые эта база данных обрабатывает ежегодно , не превышало 100 за последние 20 лет и очень (действительно, очень высоко) маловероятно, что, конечно, если оно превысит 99, то поле сможет продолжайте с дополнительной цифрой, и интерфейс / процедура сможет перейти более 99, так что это не так, как будто это что-то меняет.

Конечно, некоторые из этих деталей я не упомянул вначале, потому что они только сузили возможности решения в соответствии с моими конкретными потребностями, попытались сохранить диапазон проблем более широким.

Nelz
источник
О какой версии SQL Server идет речь?
Макс Вернон
Почему это нужно хранить в таблице, если оно нигде не используется в качестве ссылки? Почему это не может быть вычисляемый столбец (сохраняемый или вычисляемый в запросе, когда это необходимо)? Что должно произойти, если у вас более 100 строк в год?
ypercubeᵀᴹ
1
С ID= 5, 6 и 7, DATE_ADDED должен быть 2016-01-01 и так далее?
Кин Шах
@ Кин выглядит так. Я исправил образец.
ypercubeᵀᴹ
Спасибо за исправление, да, это были записи 2016 года, и сейчас я использую SQL Server 2005. @ YperSillyCubeᵀᴹ Это в основном вопрос поиска лучшего решения, поэтому на самом деле любое предложение будет оценено.
Нельц

Ответы:

6

Вы можете использовать таблицу ключей для хранения возрастающей части вашего второго столбца идентификаторов. Это решение не опирается на какой-либо код на стороне клиента и автоматически поддерживает многолетнюю работу; когда @DateAddedпараметр переходит в ранее неиспользованный год, он автоматически начинает использовать новый набор значений для ID2столбца на основе этого года. Если процедура используется для вставки строк предыдущих лет, эти строки будут вставлены с «правильными» значениями для приращения. Процесс GetNextID()предназначен для корректной обработки возможных взаимоблокировок, передавая только вызывающей стороне ошибку, если при попытке обновления tblIDsтаблицы возникают 5 последовательных взаимоблокировок .

Создайте таблицу для хранения одной строки в год, содержащей текущее используемое значение идентификатора, а также хранимую процедуру для возврата нового значения:

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

Ваша таблица вместе с процедурой для вставки в нее строк:

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

Вставьте пример данных:

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

Показать обе таблицы:

SELECT *
FROM dbo.Cond;

SELECT *
FROM dbo.tblIDs;

Результаты:

введите описание изображения здесь

Таблица ключей и хранимый процесс происходят из этого вопроса.

Макс Вернон
источник
Вы устанавливаете уровень изоляции транзакции, но не открываете транзакцию явно. Кроме того, если два одновременных сеанса попытались вставить одну и ту же (IDName, LastID)строку, это приведет к тупику или к одной из транзакций, нарушающих PK? Если последнее, возможно, имело бы смысл дать этой транзакции еще один шанс (чтобы она в итоге получила идентификатор 2).
Андрей М
И еще одна вещь, я бы, вероятно, @NewIDявно установил значение null в начале цикла: если транзакция, которая пытается вставить строку, становится жертвой взаимоблокировки, она не будет пытаться вставить строку на следующей итерации, потому @NewIDчто уже будет иметь был установлен в 1 (который не равен NULL, и поэтому ветка INSERT будет опущена).
Андрей М
На самом деле уровень изоляции транзакции вообще не нужно устанавливать; Я удалю это. Я не понимаю, как два одновременных сеанса могли бы вставить одно и то же значение в tblIDsтаблицу, поскольку эта таблица обновляется с помощью одной атомарной операции; "Причудливое" обновление.
Макс Вернон
Неплохая идея по настройке @NewID = NULLв начале цикла.
Макс Вернон
Полагаю, теоретически самое первое действие на новый год может оказаться тупиковым.
Макс Вернон