您可以使用此触发器,例如:
CREATE TRIGGER dbo.TR_InvalidateOldRows
ON CustomerHistory
AFTER INSERT
AS
BEGIN
SET NOCOUNT ON;
-- Debug
--SELECT *
--FROM inserted
UPDATE ch
SET ValidTo = GETDATE(), ModifiedOn = GETDATE()
FROM (
SELECT TOP 1 Id, CustomerId, Name, ValidFrom, ValidTo, CreatedOn, ModifiedOn
FROM (
SELECT Id, CustomerId, Name, ValidFrom, ValidTo, CreatedOn, ModifiedOn
FROM CustomerHistory AS ch
WHERE ch.CustomerId = (SELECT CustomerId FROM inserted)
EXCEPT
SELECT Id, CustomerId, Name, ValidFrom, ValidTo, CreatedOn, ModifiedOn
FROM inserted
) as allRows
ORDER BY ID DESC
) as oldRow
INNER JOIN CustomerHistory as ch
ON oldRow.id = ch.Id
END
GO
使用以下方式填充演示数据:
INSERT INTO CustomerHistory (CustomerId, Name, ValidFrom, ValidTo, CreatedOn, ModifiedOn)
VALUES (1,'AAB', GETDATE(),DATEADD(year,1,GETDATE()), GETDATE(), GETDATE())
表格中填满了:
Id CustomerId Name ValidFrom ValidTo CreatedOn ModifiedOn
----------- ----------- ---------- ----------------------- ----------------------- ----------------------- -----------------------
1 1 AAB 2015-07-04 13:21:34.500 2016-07-04 13:21:34.500 2015-07-04 13:21:34.500 2015-07-04 13:21:34.500
如果您再次运行插入,它将导致:
Id CustomerId Name ValidFrom ValidTo CreatedOn ModifiedOn
----------- ----------- ---------- ----------------------- ----------------------- ----------------------- -----------------------
1 1 AAB 2015-07-04 13:21:34.500 2015-07-04 13:22:02.163 2015-07-04 13:21:34.500 2015-07-04 13:22:02.163
2 1 AAB 2015-07-04 13:22:02.153 2016-07-04 13:22:02.153 2015-07-04 13:22:02.153 2015-07-04 13:22:02.153
此触发器只会使特定 CustomerId 的所有旧行无效。
如果您想在发生更新时另外设置 ModifiedOn 日期,您可以创建这个额外的触发器:
CREATE TRIGGER dbo.TR_UpdateModifiedOn
ON CustomerHistory
AFTER UPDATE
AS
BEGIN
SET NOCOUNT ON;
-- Debug
--SELECT *
--FROM inserted
UPDATE ch
SET ModifiedOn = GETDATE()
FROM inserted as i
INNER JOIN CustomerHistory as ch
ON i.id = ch.Id
END
GO
顺便说一句,处理这个问题的过程会比触发器更好。但在某些情况下,您无法提供程序。例如,如果用户将使用SSMS、Access 操作数据或通过任何其他应用程序直接更新。
根据反馈进行编辑
我保留上面的旧代码,以防其他人需要上述解决方案。在您的情况下,您只想阻止所有更新,除非它们仅更新 ModifiedOn 和 ValidTo 列。
在这种情况下,这个触发器会解决它:
CREATE TRIGGER dbo.TR_InsteadUpdate
ON CustomerHistory
INSTEAD OF UPDATE
AS
BEGIN
SET NOCOUNT ON;
-- Add your conditions here
IF (
-- e.g. Updated a row without modifieng the ModifiedOn Column
SELECT COUNT(*)
FROM inserted as i
INNER JOIN deleted as d
ON i.Id = d.Id
AND i.CustomerId = d.CustomerId
AND i.Name = d.Name
AND i.ValidFrom = d.ValidFrom
AND i.CreatedOn = d.CreatedOn
AND i.ValidTo <> d.ValidTo
AND i.ModifiedOn <> d.ModifiedOn
) = 0 BEGIN
-- Not allowed, rollback
RAISERROR(N'Not allowed!',16,1) WITH NOWAIT
ROLLBACK TRANSACTION
END
-- Otherwise update it
UPDATE ch
SET ModifiedOn = i.ModifiedOn, ValidTo = i.ValidTo
FROM CustomerHistory AS ch
INNER JOIN inserted AS i
ON i.Id = ch.Id
END
GO
这段代码会抛出异常:
-- not allowed
UPDATE CustomerHistory
SET Name = N'EEE'
WHERE id = 2
虽然这个可以工作:
-- allowed
UPDATE CustomerHistory
SET ModifiedOn = GETDATE(),
ValidTo = DATEADD(day,1,GETDATE())
WHERE id = 2
另一个添加可以是INSTEAD OF-Trigger 内的WHERE-clause IF-clause。如果插入时间超过 60 秒(例如),这将阻止对上述两个列的更新。这可以通过将此行添加到IF 来实现:
WHERE DATEDIFF(SECOND,d.CreatedOn,i.ModifiedOn) < 60
这段代码的结果:
CREATE TRIGGER dbo.TR_InsteadUpdate
ON CustomerHistory
INSTEAD OF UPDATE
AS
BEGIN
SET NOCOUNT ON;
-- Add your conditions here
IF (
-- e.g. Updated a row without modifieng the ModifiedOn Column
SELECT COUNT(*)
FROM inserted as i
INNER JOIN deleted as d
ON i.Id = d.Id
AND i.CustomerId = d.CustomerId
AND i.Name = d.Name
AND i.ValidFrom = d.ValidFrom
AND i.CreatedOn = d.CreatedOn
AND i.ValidTo <> d.ValidTo
AND i.ModifiedOn <> d.ModifiedOn
WHERE DATEDIFF(SECOND,d.CreatedOn,i.ModifiedOn) < 60
) = 0 BEGIN
-- Not allowed, rollback
RAISERROR(N'Not allowed!',16,1) WITH NOWAIT
ROLLBACK TRANSACTION
END
-- Otherwise update it
UPDATE ch
SET ModifiedOn = i.ModifiedOn, ValidTo = i.ValidTo
FROM CustomerHistory AS ch
INNER JOIN inserted AS i
ON i.Id = ch.Id
END
GO
添加非常广泛的表格
如果您有一个包含许多列的非常宽泛的表,并且您不会维护所有列而只想添加允许的列,您可能可以使用以下触发器,它使用哈希来比较旧行和新行.它的工作原理与上述相同,但使用的是动态哈希算法。
CREATE TRIGGER dbo.TR_InsteadUpdate
ON CustomerHistory
INSTEAD OF UPDATE
AS
BEGIN
SET NOCOUNT ON;
DECLARE @sql nvarchar(max), @hash_before varbinary(max),
@hash_after varbinary(max), @columnlist nvarchar(max),
@paramDefinition nvarchar(500) = N'@hash_value varbinary(max) OUTPUT';
SELECT @columnlist = COALESCE(
@columnlist + N'+ISNULL(CONVERT(nvarchar(max),'+ COLUMN_NAME + N'),N'''')',
N'ISNULL(CONVERT(nvarchar(max),'+ COLUMN_NAME + N'),N'''')')
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = N'CustomerHistory'
-- Which columns are allowed to be updated?
AND COLUMN_NAME NOT IN(N'ModifiedOn',N'ValidTo')
-- needed due to scope of deleted and inserted
SELECT * INTO #deleted FROM deleted
-- Get the hash-value for the before-values
SET @sql = N'
SELECT @hash_value = HASHBYTES(''SHA1'','+@columnlist+')
FROM #deleted'
EXECUTE sp_executesql @sql, @paramDefinition, @hash_value = @hash_before OUTPUT;
DROP TABLE #deleted
SELECT * INTO #inserted FROM inserted
-- Get the hash-value for the after-values
SET @sql = N'
SELECT @hash_value = HASHBYTES(''SHA1'','+@columnlist+')
FROM #inserted'
EXECUTE sp_executesql @sql, @paramDefinition, @hash_value = @hash_after OUTPUT;
DROP TABLE #inserted
SELECT @hash_before, @hash_after
IF (@hash_before <> @hash_after) BEGIN
-- Not allowed, rollback
RAISERROR(N'Not allowed!',16,1) WITH NOWAIT
ROLLBACK TRANSACTION
END
-- Otherwise update it
UPDATE ch
SET ModifiedOn = i.ModifiedOn, ValidTo = i.ValidTo
FROM CustomerHistory AS ch
INNER JOIN inserted AS i
ON i.Id = ch.Id
END
GO