Создание собственного идентификатора при обновлении SQL Server (триггер?) - PullRequest
1 голос
/ 16 августа 2011

У меня есть таблица для запросов, и после того, как они будут одобрены / отклонены, им необходимо назначить собственный последовательный идентификатор, например:

MYCODE-11-0001
MYCODE-11-0002
MYCODE-11-0003 

, где MYCODE не изменяется, но 11 - текущий год, а 0001 - последовательно генерируемый номер, назначенный на год. Так что каждый год он начинается в 0001.

Я никогда не имел дело с триггерами или хранимыми процедурами в SQL Sever, поэтому у меня была идея сделать это в коде через Linq-to-SQL на VB.net, и у меня есть другая таблица в SQL Server, которая отслеживает Next ID

ID     NAME          NextID
1      Request2011    102
2      Request2012    1
3      Request2013    1

Если они изменили статус на веб-сайте, приложение выбирает имя на основе текущего года, получает NextID и генерирует RequestID. LINQ даже перехватит, если NextID изменилось с момента получения значения, добавления его и попытки сохранить это новое значение.

Уверен, это сработает, но я не совсем доволен, потому что предполагается, что все будет из моего приложения. Я бы чувствовал себя намного лучше, если бы это было сделано на уровне базы данных, и даже лучше, если бы он не опирался на эту таблицу NextID, которая была у меня.

Есть ли что-нибудь с триггерами и процедурами хранения, которые не были бы слишком сумасшедшими, что позволило бы мне генерировать эти пользовательские идентификаторы? У меня есть смутное представление о том, что я могу запустить триггер при обновлении StatusID для запуска хранимой процедуры для выполнения некоторых тяжелых работ (генерируется RequestID, если еще не установлено), но если мне все еще приходится полагаться на NextID таблица, тогда я должен беспокоиться о блокировке / разблокировке таблицы, а также правильно?

Просто надеюсь, что кто-то посоветуется с людьми, которые знают SQL Server гораздо лучше, чем я.

Примечание: Пользовательский идентификатор назначается только во время утверждения / отклонения запроса, а не когда он был вставлен в базу данных. Каждый запрос имеет свой уникальный автоматически сгенерированный идентификатор PK.


ОБНОВЛЕНИЕ: Возможно, мне придется придерживаться решения кода, так как оно делает все возможное для меня, когда дело касается параллелизма, но я написал триггер только для того, чтобы я мог учиться.

/* --------------------
        SETUP 
--------------------*/
CREATE TABLE [dbo].[TestData](
    [ID] [int] IDENTITY(1,1) NOT NULL,
    [data] [varchar](100) NULL,
    [RequestID] [varchar](25) NULL,
    [StatusID] [int] NULL
    )

    INSERT INTO TestData
           ([data],[StatusID])
     VALUES
           ('test',1)
GO


CREATE TABLE [dbo].[NextID](
    [ID] [int] IDENTITY(1,1) NOT NULL,
    [Name] [varchar](75) NULL,
    [NextID] [int] NULL,
    [DateModified] [date] NULL,
    [ModifiedBy] [varchar](75) NULL
    )
    INSERT INTO NextID
           ([Name],[NextID])          
     VALUES('RequestID2011',1)           
GO

/* --------------------
        TRIGGER
--------------------*/
IF EXISTS (SELECT name FROM sys.objects
      WHERE name = 'UpdateStatusID' AND type = 'TR')
   DROP TRIGGER dbo.UpdateStatusID;
GO

CREATE TRIGGER UpdateStatusID
ON dbo.TestData
AFTER UPDATE 
AS 
IF ( UPDATE (StatusID))
BEGIN
    DECLARE @nextId int
    DECLARE @nextIdName varchar(50)

    /* -- Get NextID based on year's count IE: RequestID2011 */
    SET @nextIdName = 'RequestID' + CONVERT(VARCHAR, YEAR(GetDate()), 50)
    SET @nextid = (Select NextID from dbo.NextID where Name = @nextIdName)

    /* -- Increment NextID */
    UPDATE dbo.NextID set NextID = @nextid + 1 WHERE Name=@nextIdName

    /* -- Set New RequestID */
    UPDATE dbo.TestData
    SET RequestID = 'MYCODE-' + RIGHT(@nextIdName,2) + '-' + CONVERT(VARCHAR, REPLICATE('0', (4- LEN(@nextid))) + @nextid, 50)
    FROM inserted i INNER JOIN dbo.TestData t
    ON i.id = t.id 

END;
GO
/* --------------------
        TEST
--------------------*/
UPDATE dbo.TestData
SET StatusID = 3
WHERE ID = 1;
GO

Чего это не делает, так это защиты от того, что кто-то изменил StatusID более одного раза (они могут сделать это, но мне нужно только сгенерировать ID один раз) или защиты от людей, одновременно захвативших NextID и имеющих проблема состояния гонки.

Это также все еще требует от меня иметь несколько записей «RequestIDXXXX» в базе данных на будущие годы.

Предупреждение: Это не обрабатывает параллелизм и это просто работа на моем пути к пониманию триггеров и SQL Server.

Ответы [ 4 ]

1 голос
/ 16 августа 2011

Вы можете попробовать что-то вроде этого:

DECLARE @CustomID VARCHAR(255)
SELECT @CustomID = 'MYCODE-' + CAST((YEAR( GETDATE()) % 100) AS CHAR(2)) + '-' + REPLICATE('0', (4 - LEN((<Sequence> + 1)))) + CAST((<Sequence> + 1) AS VARCHAR(5))

РЕДАКТИРОВАТЬ

Я не знаю, как вы хотите получить последовательность, но если вы хотите потянуть ееиз подсчета строк в таблице вы можете сделать что-то вроде этого:

SELECT COUNT(SomethingID) --Optionally add +1
FROM   SomethingTable
WHERE  DATEDIFF(YEAR, SomeDateColumn, GETDATE()) = 0
1 голос
/ 16 августа 2011

Вы действительно можете сделать это с помощью триггера на вашем столе, см. Ниже:

create table NextID (
    id  int,
    name    varchar(10),
    nextid  int
)

create table TestData (
    sequence    varchar(14) NOT NULL,
    data    varchar(10)
)

insert into NextID
values(1, 'Req2011', 1)


--Important stuff starts here
ALTER TRIGGER MySequence
ON TestData
INSTEAD OF INSERT
AS
DECLARE @nextid int

SET @nextid = (select nextid from NextID ) - 1

UPDATE NextID
SET nextid = nextid + (select COUNT(*) from inserted)
WHERE id = 1;

WITH cte AS (
    select ROW_NUMBER() OVER (ORDER BY (select 1)) + @nextid as sequence, i.data
    from inserted i
)
insert into TestData
select 'MYCODE-' + RIGHT(YEAR(GETDATE()), 2) + '-' + RIGHT('000' + CAST(sequence as varchar), 4), data
from cte;

Это довольно надежный способ сделать это. Обратите внимание, что триггер записан так, что позволяет вставлять более одной записи одновременно.

Ссылка: http://msdn.microsoft.com/en-us/library/ms189799.aspx

http://www.sqlmag.com/article/sql-server/nondeterministic-row-numbers

1 голос
/ 16 августа 2011

Да, наличие такой таблицы для следующего идентификатора является горячей точкой и зачастую узким местом - если все сделано правильно. Если нет, то вы получите дубликаты ID ....

В данный момент SQL Server 2008 R2 не очень подходит для вас. Это может измениться в SQL Server 2011 (или, возможно, в SQL Server 2012) под кодовым названием «Denali». Denali представит последовательности , которые позволят вам создавать последовательные идентификаторы под контролем ядра базы данных, и они гарантированно будут уникальными. Вы также можете сбросить последовательности обратно на 1 1 января каждого нового года.

Ознакомьтесь с публикацией Аарона Бертранда в блоге на SQL Server vNext (Denali): Использование SEQUENCE для получения дополнительной информации о последовательностях.

На данный момент, да, я думаю, что ваш подход с установкой этих идентификаторов со стороны клиента с использованием Linq-to-SQL или внутри триггера, вероятно, является наиболее подходящим подходом (я бы предпочел решение триггера, сам) , Я думаю, что одного триггера INSTEAD OF INSERT для этой таблицы будет достаточно - если вы вставите новую строку, получите новый требуемый идентификатор запроса и установите для него значения, которые вы только что добавили.

0 голосов
/ 16 августа 2011

Я бы предположил, что это не будет "id", в смысле первичного ключа.

Вместо этого вы можете использовать постоянный вычисляемый столбец.Кое-что с эффектом:

ALTER TABLE myTable
ADD CustomID AS (

    'MYCODE-' + CAST(RIGHT(100 + year([date_field]),2) as varchar(2)) + CAST(RIGHT(10000 + [pk_field],4) as varchar(4))

) PERSISTED

Таким образом, работа выполняется для вас каждый раз, когда в таблицу добавляется новая запись.

Здесь предостережение заключается только в том, что при обновлениистолбец [pk_field] (что маловероятно ...?), ваш вычисляемый столбец будет пересчитан заново.Но в противном случае, пока вы используете «PERSISTED», он останется неизменным.

EDIT Я заменил getdate() на [date_field] выше.Я все еще думаю, что вычисляемый столбец является хорошим вариантом для вас, но предостережение заключается в том, что вам нужно поле даты в записи (date_inserted, или что у вас), так как getdate() (или другие недетерминированные значения) не может использоваться.

...