触发以阻止一些更新并允许插入 [英] Trigger to block some updates and allow inserts

查看:66
本文介绍了触发以阻止一些更新并允许插入的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个SQL Server表,该表可以跟踪客户名称的更改:

I've got a SQL Server table which keeps track of customer name changes:

CREATE TABLE CustomerHistory 
(
 Id INT IDENTITY(1,1) PRIMARY KEY,
 CustomerId INT NOT NULL,
 Name VARCHAR(255) NOT NULL,
 ValidFrom DATETIME NOT NULL,
 ValidTo DATETIME NOT NULL,
 CreatedOn DATETIME NOT NULL,
 ModifiedOn DATETIME NOT NULL
)

INSERT INTO CustomerHistory (CustomerId, Name, ValidFrom, ValidTo, CreatedOn, ModifiedOn ) 
VALUES (1, 'ABC', '1900-01-01','2999-12-31', '2015-07-03 11:29:23.000', '2015-07-03 11:29:23.000')

应用程序允许用户以两种方式进行更改,即通过更改当前记录( 名称 ModifiedOn 已更新)或通过插入新记录

Application allows user to make changes in two ways i.e. either by changing current record (Name and ModifiedOn are updated) or by inserting new record

INSERT INTO CustomerHistory (CustomerId, Name, ValidFrom, ValidTo, CreatedOn, ModifiedOn) 
VALUES (1,'AAB', '2015-07-04','2999-12-31', '2015-07-04 12:29:23.000', '2015-07-04 12:29:23.000')

并更新上一个(名称,ValidTo,ModifiedOn 已更新)。由于以第一种方式进行了一些不需要的更改(仅更新当前记录),因此我需要将其阻止,因此以第二种方式进行每个更改(插入新记录并更新先前的记录)。我需要使用触发器来做到这一点,以便用户获得特殊的错误信息。

and updating previous one (Name, ValidTo, ModifiedOn are updated). Due to several unwanted changes done in the first way (updating current record only) I need to block it so each change made is done in the second way (inserting new record and updating previous one). I need to do it with trigger so users gets special error information.

任何想法如何应对?

推荐答案

例如,可以使用此触发器:

You can use this trigger for example:

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

如果您运行插入再次将导致以下结果:

If you run the Insert again it will result in this:

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 的旧行。

This trigger will just invalidate all older rows of a specific CustomerId.

如果您还想设置 ModifiedOn 日期,如果发生更新,则可以创建此附加触发器:

If you want additionally to set the ModifiedOn date if an update occurs you can create this additional trigger:

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 或通过任何其他应用程序直接更新来操纵数据。

By the way, a procedure which handles this would be better than a trigger. But in some cases you can't provide a procedure. For example if users will manipulate the data using SSMS, Access or direct updates through any other applications.

根据反馈进行编辑

我将旧代码放在上面,以防万一其他人需要如上所述的解决方案。在您的情况下,您只想阻止所有更新,除了它们仅更新 ModifiedOn ValidTo

I leave the old code above just in case someone else needed a solution like described above. In your case you just want to block all Updates except they update only the ModifiedOn and the ValidTo columns.

在这种情况下,此触发器将解决该问题:

In this case this trigger will solve it:

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

另一个附加项可以是<$ c $内的 INSTEAD OF -Trigger内的 WHERE 子句c> IF -条款。如果插入时间早于60秒(例如),这将阻止在提到的两列上进行更新。可以通过将此行添加到 IF

Another addition can be an WHERE-clause inside the INSTEAD OF-Trigger inside the IF-clause. Which will prevent updates on the two mentioned columns, if the Insert is older than 60 seconds (for example). This can be achieved by adding this row to the 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

添加真正宽泛的表

如果您有一个包含很多列的真正宽泛的表,并且您将不会维护所有列,要添加允许的列,您可能可以使用以下触发器,该触发器使用哈希来比较旧行和新行。它将按照上述相同的原理工作,但使用动态哈希算法。

If you have a really broad table with many columns and you won't to maintain all columns and just want to add the allowed columns you can probably use the following trigger which uses a hash to compare the old and the new row instead. It will work after the same principle of the above one but uses a dynamic hash algorithm.

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

这篇关于触发以阻止一些更新并允许插入的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆