仅在未提供修改日期时更新修改日期触发器

时间:2014-01-31 20:07:39

标签: sql-server triggers

我有一些表上有“创建”和“修改”日期。如果应用层未提供修改日期,我希望能够更新后者。我们需要让应用层能够在离线事务(例如以后没有互联网连接的设备)发生的情况下将修改日期设置为特定值,但我们无法始终确保应用程序开发人员始终记得在应用程序代码中设置修改日期。

鉴于下表:

CREATE TABLE [dbo].[Tests](
    [TestID] [int] IDENTITY(1,1) NOT NULL,
    [Name] [nvarchar](50) NOT NULL,
    [CreatedDate] [datetime2](7) NOT NULL,
    [ModifiedDate] [datetime2](7) NOT NULL,
 CONSTRAINT [PK_Tests] PRIMARY KEY CLUSTERED 
(
    [TestID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]

GO

ALTER TABLE [dbo].[Tests] ADD  CONSTRAINT [DF_Tests_CreatedDate]  DEFAULT (getutcdate()) FOR [CreatedDate]
GO

ALTER TABLE [dbo].[Tests] ADD  CONSTRAINT [DF_Tests_ModifiedDate]  DEFAULT (getutcdate()) FOR [ModifiedDate]
GO

似乎当更新中没有提供“修改日期”时,而不是更新或抛出异常,只是保持不变。

示例测试用例:

生成LINQ到实体代码:

exec sp_executesql N'update [dbo].[Tests]
set [Name] = @0
where ([TestID] = @1)
',N'@0 nvarchar(50),@1 int',@0=N'Test_635267931494843908',@1=3

应将修改日期更新为GETUTCDATE()

SQL代码:

UPDATE Tests
SET Name = 'Test' + CONVERT(nvarchar(255), NEWID()) 
WHERE TestID =3

应将修改日期更新为GETUTCDATE()

修改日期的SQL代码:

UPDATE Tests
SET Name = 'Test' + CONVERT(nvarchar(255), NEWID()),
ModifiedDate = '2014-01-31 19:10:48'
WHERE TestID =3

应将修改日期更新为“2014-01-31 19:10:48”

我尝试了以下内容:

CREATE TRIGGER 
    [dbo].[ModifiedDateUpdateTrigger]
ON 
    [dbo].[Tests]
AFTER UPDATE
AS 
BEGIN

    UPDATE dbo.Tests 
    SET ModifiedDate = GETUTCDATE()
    FROM dbo.Tests as t
    INNER JOIN inserted as i on i.TestID = t.TestID
    INNER JOIN deleted as d on d.TestID = t.TestID
    WHERE t.ModifiedDate <> i.ModifiedDate
        AND d.ModifiedDate <> i.ModifiedDate
END

CREATE TRIGGER 
    [dbo].[ModifiedDateUpdateTrigger]
ON 
    [dbo].[Tests]
FOR UPDATE
AS 
BEGIN
    DECLARE @m datetime2(7) = (select MODIFIEDDATE FROM inserted)
    print @m
    IF (select MODIFIEDDATE FROM inserted)  IS  NULL
    BEGIN
        RETURN
    END

    UPDATE dbo.Tests 
    SET ModifiedDate = GETUTCDATE()
    FROM INSERTED i
    WHERE i.TestID = Tests.TestID
END

最后:

CREATE TRIGGER 
    [dbo].[ModifiedDateUpdateTrigger]
ON 
    [dbo].[Tests]
FOR UPDATE
AS 
BEGIN
    DECLARE @m datetime2(7) = (select MODIFIEDDATE FROM inserted)
    print @m
    IF (select MODIFIEDDATE FROM inserted) <> (SELECT i.ModifiedDate FROM Tests as t INNER JOIN inserted as i on t.TestID = i.TestID)
    BEGIN
        RETURN
    END

    UPDATE dbo.Tests 
    SET ModifiedDate = GETUTCDATE()
    FROM INSERTED i
    WHERE i.TestID = Tests.TestID
END

这些都不适用于所有愿望测试案例。

2 个答案:

答案 0 :(得分:2)

你不能使用任何假装inserted只能包含一行的方法(如果我在一个语句中更新多个测试怎么办?)。相反,您需要执行基于联接的更新,并且只需使用COALESCE()来确定是否为该列提供了不同的值(如果没有,则将其替换为GETUTCDATE())。这是我更喜欢的方法:

CREATE TRIGGER  [dbo].[ModifiedDateUpdateTrigger]
ON [dbo].[Tests]
FOR UPDATE
AS 
BEGIN
  SET NOCOUNT ON;

  UPDATE t
    SET ModifiedDate = COALESCE(NULLIF(i.ModifiedDate,d.ModifiedDate), GETUTCDATE())
    FROM dbo.Tests AS t
    INNER JOIN inserted AS i
     ON t.TestID = i.TestID
    INNER JOIN deleted AS d
     ON t.TestID = d.TestID;
END
GO

更复杂的原因是这个。考虑一个简单的表:

CREATE TABLE dbo.Tests(TestID INT, ModifiedDate DATETIME, x VARCHAR(1));

现在,有两行:

INSERT dbo.Tests(TestID) SELECT 1,GETDATE() UNION ALL SELECT 2,GETDATE();

我们希望在此方案中更新ModifiedDate列:

UPDATE dbo.Tests SET x = 'y';

但在这种情况下:

UPDATE dbo.Tests SET x = 'y', ModifiedDate = CASE
  WHEN TestID = 1 THEN '19000101' ELSE ModifiedDate END
  WHERE TestID IN (1,2);

这两个都可以作为触发器的多行更新得到充分处理,但如果您只是假设inserted中提供的值是新值,则后者可能会被错误处理。您需要将其与deleted进行比较,以确保它实际已更改。唯一让这很难做到的事情是什么?将值明确设置为NULL(嗯,不禁用触发器)。 :-)在这种情况下,它实际上将替换您使用NULL传递的GETUTCDATE()。并不是说你曾经想要这样做,但我不想让它没有说明。


更新

在评论中,您提到了一种情况,如果您在后续UPDATE语句中对相同的ModifiedDate值进行了硬编码,则需要阻止ModifiedDate“碰撞”。我无法找到区分后触发器的方法(因为表已经被更改,并且此时无法判断这是来自当前更新还是之前的更新),但我确实找到了一种方法区分而不是触发器。因此,如果您可以更改为触发器而不是触发器,则可以执行此操作 - 唯一的另一个复杂因素是您还必须考虑更新语句中可能未明确提及的任何其他列,或者您希望以不同方式处理的任何其他列,具体取决于之前/之后的值。这是触发器:

CREATE TRIGGER  [dbo].[ModifiedDateUpdateTrigger]
ON [dbo].[Tests]
INSTEAD OF UPDATE
AS 
BEGIN
  SET NOCOUNT ON;

  UPDATE src 
    SET src.Name = i.Name, /* other columns that might get updated */
      src.ModifiedDate = CASE 
        WHEN i.ModifiedDate <> src.ModifiedDate THEN CASE 
          WHEN UPDATE(ModifiedDate) THEN i.ModifiedDate ELSE GETUTCDATE() END
        WHEN i.ModifiedDate = src.ModifiedDate THEN CASE 
          WHEN NOT UPDATE(ModifiedDate) THEN GETUTCDATE() ELSE src.ModifiedDate END
        ELSE GETUTCDATE() 
      END
    FROM dbo.Tests AS src
    INNER JOIN inserted AS i
    ON i.TestID = src.TestID
    INNER JOIN deleted AS d
    ON i.TestID = d.TestID;
END
GO

这是一个演示:http://sqlfiddle.com/#!3/4a00b5/1

我不是100%确信ELSE条件是必需的,但是我可以在今天投入测试时耗尽精力。

答案 1 :(得分:1)

对于同样的问题,我有另一种方法;如果应用程序代码未更新修改日期字段,则在更新后触发器中出现raiserror。因此,应用程序开发人员在开发时必须尊重数据库架构。另一种方法是使用类似的触发器,但不是引发错误,而是发出更新语句以自动设置修改日期。

示例1。

create trigger dbo.t_myTable
on dbo.myTable
after update
AS
begin
    set nocount on

    if ( not update(ModifiedDate) )
    begin
        raiserror( 'The ModifiedDatefield was not modified during update.', 16, 1 );
        rollback transaction
    End


end

示例2。

create trigger dbo.t_myTable
on dbo.myTable
after update
AS
begin
    set nocount on

    if ( not update(ModifiedDate) )
    begin
        update t
        set t.ModifiedDate = default
        from dbo.myTable t
        inner join inserted i
        on t.Id = i.Id -- where id is the primary key 
    End


end