sql-server - 仅当未提供修改日期时才触发 UPDATE 修改日期

标签 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-to-Entities 代码:

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

这些都不适用于所有想要的测试用例。

最佳答案

您不能使用任何假装 inserted 只能包含一行的方法(如果我在一条语句中更新多个测试怎么办?)。相反,您需要执行基于连接的更新,您可以只使用 COALESCE() 来确定是否为该列提供了一个不同值(当它没有t,将其替换为 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(嗯,不禁用触发器)。 :-) 在这种情况下,它实际上会用 GETUTCDATE() 替换您传递的 NULL。并不是说您会想要那样做,而是我不想不说。


更新

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

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 条件是必需的,但我已经筋疲力尽了,我可以为今天的测试投入时间。

关于sql-server - 仅当未提供修改日期时才触发 UPDATE 修改日期,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/21489102/

相关文章:

sql-server - 无法更新包含特殊字符的 varchar 列

c# - 尝试使用登录前握手确认时超时期限已过。 (一天只有几次)

sql-server - DBCC 命令在程序内部不起作用

jquery - 通过 jquery 触发器传递对象

sql - 当我一次又一次用相同的值更新表时会发生什么?

mysql - 触发器从 plpgSQL 到 MySQL

sql - 如何检查 SQL 结果是否包含换行符?

mysql - 与 SQL Server 相比,如何提高 mySQL 中大量更新的速度?

mysql - 插入后触发器用另一个表中的数据更新同一个表

REPLACE() 触发器中的 MySQL 语法错误