我有一些表上有“创建”和“修改”日期。我希望仅当应用层未提供修改日期时才能够更新后者。我们需要让应用层能够在离线交易(例如没有互联网连接的设备在稍后同步)发生的情况下将修改日期设置为特定值,但我们不能总是确保任何应用程序开发人员都将永远记得在应用程序代码中设置修改日期。
给出下表:
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/