0

场景/背景:

我正在尝试创建一个“测试”表。出于这个问题的目的,我的表将只有 5 列定义如下:

 CREATE TABLE TestTable
 (
      Id UNIQUEIDENTIFIER DEFAULT NEWID() NOT NULL,
      Name VARCHAR(75) NOT NULL,
      DateRequested DATETIME NOT NULL DEFAULT GETDATE(),
      TestYear AS YEAR(DateRequested) PERSISTED NOT NULL, -- computed column that shows the year the test was requested. I want to persist this column so I can index on it if need be.
      TestNumber CHAR(4) NOT NULL DEFAULT '0000', -- need this to auto increment but also needs to reset for the first test of the year.
      CONSTRAINT TestTablePK PRIMARY KEY(Id)
 );
 GO

我的要求是我希望“TestNumber”根据年份“自动递增”。例如:

 GUID, Test 1 in Old Yr, 2013-01-01 05:00:00.000, 2013, 0001
 GUID, Test 2 in Old Yr, 2013-12-25 11:00:00.000, 2013, 0002
 GUID, Test 3 in Old Yr, 2013-12-26 09:00:00.000, 2013, 0003
 ...., ................, ......................., ...., N
 GUID, Test N in Old Yr, 2013-12-31 09:00:00.000, 2013, N+1
 GUID, Test 1 in New Yr, 2014-01-01 11:00:00.000, 2014, 0001   <-- reset to 1

我在想这将是一个自动增量列,但是基于这是新年的第一次测试,我将如何重置它?所以到目前为止我的错误解决方案是一个“而不是插入”触发器,定义如下:

 CREATE TRIGGER InsteadOfInsertTrigger ON dbo.TestTable
 INSTEAD OF INSERT AS
 BEGIN
      -- Get the year of the test request being inserted from the pseudo-insert table.
      DECLARE @TestYear INT;
      SET @TestYear = (SELECT YEAR(DateRequested) FROM inserted);

      -- Grab the maximum TestNumber from TestTable based on the year
      -- that we are inserting a record for.
      DECLARE @MaxTestNumber INT;
      SET @MaxTestNumber = (SELECT MAX(TestNumber) FROM dbo.TestTable WHERE TestYear = @TestYear);

      -- If this is the first test of the year being inserted it is a special case
      IF @MaxTestNumber IS NULL
      BEGIN
           SET @MaxTestNumber = 0;
      END;

      -- Here we take the MaxTestNumber, add 1 to it, and then pad it with 
      -- the appropriate number of zero's in front of it 
      DECLARE @TestNumber VARCHAR(4);
      SET @TestNumber = (SELECT RIGHT('0000' + CAST((@MaxTestNumber + 1) AS VARCHAR(4)), 4));

      INSERT INTO dbo.TestTable(Name, DateRequested, TestNumber)
      SELECT Name, DateRequested, @TestNumber FROM inserted;
 
 END;
 GO

现在这里有一些 DML 显示了正在运行的触发器:

 INSERT INTO TestTable(Name, DateRequested)
 VALUES('Some Test', '05-05-2013');
 INSERT INTO TestTable(Name, DateRequested)
 VALUES('Some Other Test', '12-25-2013');
 INSERT INTO TestTable(Name, DateRequested)
 VALUES('Blah Blah', '12-31-2013');
 INSERT INTO TestTable(Name, DateRequested)
 VALUES('Foo', '01-01-2014');
 SELECT * FROM TestTable ORDER BY TestYear ASC, TestNumber ASC;

查询结果图片

因此,您可以看到我的触发器适用于单行插入,但敏锐的眼睛将能够判断它不适用于多行插入。

 CREATE TABLE TempTestTable
 (
      Name VARCHAR(75) NOT NULL,
      DateRequested DATETIME NOT NULL DEFAULT GETDATE()
 );
 GO

 INSERT INTO TempTestTable(Name, DateRequested)
 VALUES('Test1', '01-01-2012');
 INSERT INTO TempTestTable(Name, DateRequested)
 VALUES('Test2', '12-25-2012');
 INSERT INTO TempTestTable(Name, DateRequested)
 VALUES('Test3', '01-01-2013');
 INSERT INTO TempTestTable(Name, DateRequested)
 VALUES('Test4', '01-01-2014');

 -- This doesnt work because it is a multi-row insert.
 INSERT INTO TestTable(Name, DateRequested)
 SELECT Name, DateRequested FROM TempTestTable;

我的问题

我意识到我可能可以使用存储过程来处理这个问题,并在更新表时强制用户使用存储过程,但我想格外小心并防止系统管理员能够使用不正确的“TestNumber”直接插入表。

所以 StackOverflow,我的问题是我怎样才能做到这一点?我会在我的替代插入触发器中使用光标吗?我正在寻找替代品。

4

1 回答 1

1

这不是我写过的最整洁的东西,但似乎可以完成这项工作:

CREATE TRIGGER InsteadOfInsertTrigger ON dbo.TestTable
 INSTEAD OF INSERT AS
 BEGIN
    ;With Years as (
        select i.TestYear,
               COALESCE(MAX(tt.TestNumber),0) as YMax
        from inserted i left join TestTable tt
               on i.TestYear = tt.TestYear 
        group by i.TestYear
    ), Numbered as (
        select i.ID,i.Name,i.DateRequested,
               RIGHT('000' + CONVERT(varchar(4),
                  ROW_NUMBER() OVER (PARTITION BY i.TestYear
                                     ORDER BY i.DateRequested,i.Id) + YMax)
               ,4) as TestNumber
        from inserted i
            inner join
            Years y
                on
                    i.TestYear = y.TestYear
    )
    insert into TestTable (Id,Name,DateRequested,TestNumber)
    select Id,Name,DateRequested,TestNumber from Numbered;
 END;

第一个CTE ( Years) 查找感兴趣的每年使用次数最多的数字。然后,第二个 CTE ( Numbered) 使用这些值来ROW_NUMBER()抵消inserted. 我选择了ORDER BY列,ROW_NUMBER()以便它尽可能具有确定性。

(我有一段时间对一件事感到困惑,但事实证明我可以使用TestYearfrominserted而不必重复YEAR(DateRequested)公式)

于 2013-04-19T13:53:56.487 回答