我有一些表上有“创建”和“修改”日期。如果应用层未提供修改日期,我希望能够更新后者。我们需要让应用层能够在离线事务(例如以后没有互联网连接的设备)发生的情况下将修改日期设置为特定值,但我们无法始终确保应用程序开发人员始终记得在应用程序代码中设置修改日期。
鉴于下表:
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
这些都不适用于所有愿望测试案例。
答案 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