【问题标题】:Issue with Trigger触发器问题
【发布时间】:2021-06-05 09:52:11
【问题描述】:

我正在使用插入触发器并且工作正常。我正在创建一个插入触发器并在tblHist 表中进行备份。

我有两张桌子:

  • tblUser - 为插入、更新和删除目的创建此表
  • tblHist - 创建此表以存储历史记录

tblUser 表格设计:

tblHist 表格设计:

然后我创建一个插入和更新触发器:

ALTER TRIGGER [dbo].[trgr_tblUser_AFTERINSERT] 
ON [dbo].[tblUser]
AFTER INSERT, UPDATE
   --,DELETE
AS 
BEGIN
    DECLARE @userid int, @username varchar(50), 
            @useraddress varchar(50), @countryname varchar(5),
            @statename varchar(50), @cityname varchar(50);
     
    BEGIN
        SELECT 
            @userid = u.userid, @username = u.username, 
            @useraddress = u.useraddress,
            @countryname = u.countryname,
            @statename = u.statename, @cityname = u.cityname 
        FROM tblUser u;
    
        INSERT INTO tblHist (userid, username, useraddress, countryname,  statename, cityname)
        VALUES (@userid, @username, @useraddress, @countryname, @statename, @cityname);  
  
        PRINT 'AFTER INSERT update trigger fired.'  
    END
END

当我在tblUser 表中插入一条记录时,它会在tblHist 表中插入一条记录 - 这工作正常。

见下文

然后我更新一条记录,然后在 tblHist 表中插入历史记录,工作正常。

但问题是当我在 trgr_tblUser_AFTERINSERT 添加删除记录功能的代码时,删除功能不起作用

当我单独创建删除触发器时,工作正常

见下文

ALTER TRIGGER [dbo].[trgr_tblUser_AFTERDELETE]
ON [dbo].[tblUser] 
FOR DELETE
AS 
BEGIN
    DECLARE @userid int, @username varchar(50), 
            @useraddress varchar(50), @countryname varchar(5),
            @statename varchar(50), @cityname varchar(50);
    
    SELECT
        @userid = u.userid, @username = u.username, 
        @useraddress = u.useraddress, @countryname = u.countryname,
        @statename = u.statename, @cityname = u.cityname 
    FROM deleted u;
    
    INSERT INTO tblHist (userid, username, useraddress, countryname, statename, cityname)
    VALUES (@userid, @username, @useraddress, @countryname, @statename, @cityname);  
    
    PRINT 'AFTER DELETE trigger fired.'     
END

我想在一个触发器中添加插入、更新、删除触发器功能但不起作用。

我正在尝试什么:

exist select 1  --- but not work

我做错了什么地方需要帮助

【问题讨论】:

  • 最简单的解决方案:创建 三个 单独的触发器 - 每个操作一次。然后你不需要做很多检查来弄清楚你正在处理什么操作。另外:在 SQL Server 中,触发器将被调用每条语句一次(而不是每行一次) - 所以当您从 InsertedDeleted 伪表中选择时 - 总是 b> 假设这些表中有多行!您当前的SELECT 只会从Deleted 中拉出一个 行-所有其他行呢?您需要使用 基于集合的 方法 - 而不是“逐行痛苦”的方法...
  • 正如 marc_s 提到的,触发器是基于 SET 的。如果您有包含行的表并执行 INSERT/UPDATE/DELETE(没有 where 过滤器),则触发器会为所有 100 行触发一次。在触发器中定义单个 @scalarVariables 将无法按您的需要工作。
  • 这是一个不错的学习示例:stackoverflow.com/questions/17116334/…
  • 有什么特殊原因可以让用户的所有列都为空?
  • 您现在已经发现(或至少被告知),您的代码不能“正常工作”。您需要改进测试和验证代码的方式;您的测试非常简单,无法发现这些重大问题。另一个注意事项 - 影响零(无)行的语句也会导致任何关联的触发器执行。

标签: sql-server triggers


【解决方案1】:

高度怀疑您的第一个触发器能否正常工作......您只是从tblUser 表中选择任意行 - 甚至不是必须刚刚插入或更新的行。 ...

我会强烈推荐这些更改:

  • 每个操作创建一个单独的触发器 - 这使得触发器更简单,因为您不需要先弄清楚您正在处理什么... .
  • ModifiedOn DATETIME2(3) 列添加到您的tblHist 以记录更改发生时的日期和时间戳
  • 还可以在您的 tblHist 中添加一个 Operation 列 - 以便您了解是什么操作(插入、更新、删除)导致了历史记录表中的此条目
  • 正确处理触发器代码中的 InsertedDeleted 伪表,考虑到它们可以(并且将!)包含多行 - 以适当的、基于集合的方式处理它们
  • 删除PRINT - 在触发器中没有意义....

代码类似于:

CREATE OR ALTER TRIGGER dbo.trgr_tblUser_AfterInsert
ON dbo.tblUser
AFTER INSERT
AS 
BEGIN
    -- do an "INSERT INTO" ...
    INSERT INTO tblHist (ModifiedOn, userid, username, useraddress, countryname,  statename, cityname)
        -- based on the "Inserted" pseudo table, and use proper set-based approach
        SELECT 
            SYSDATETIME(),
            i.userid, i.username, i.useraddress, i.countryname, i.statename, i.cityname 
        FROM   
            Inserted i;
    END
END

CREATE OR ALTER TRIGGER dbo.trgr_tblUser_AfterDelete
ON dbo.tblUser
AFTER DELETE
AS 
BEGIN
    INSERT INTO tblHist (ModifiedOn, userid, username, useraddress, countryname,  statename, cityname)
        SELECT 
            SYSDATETIME(),
            d.userid, d.username, d.useraddress, d.countryname, d.statename, d.cityname 
        FROM   
            Deleted d;
    END
END

【讨论】:

  • 感谢您的解释,但是更新触发器呢?意味着我在哪个地方添加更新关键字来更新触发器?你能描述一下我可以在哪个地方添加更新触发器来更新记录
  • CREATE OR ALTER TRIGGER dbo.trgr_tblUser_AfterUpdate ON dbo.tblUser AFTER UPDATE - 或者你是什么意思?抱歉,我不清楚你真正要问的是什么......
【解决方案2】:

如果要检查它是插入、更新还是删除,则必须检查 inserteddeleted 伪表的行数 - 对于插入,记录仅存在于 inserted 中,例如删除它们只存在于deleted。更新具有两者,因此您可以区分旧值(已删除)和新值(插入)

让您的生活轻松;在您的 hist (应该称为 UserHist,不是吗?)表中放入两倍于您的用户表的列,并使它们例如old_username, new_username.. 插入 inserteddeleted 表之间的完全外连接的结果,这样你可以判断它是插入、更新还是删除,特别是对于插入,什么改成什么

或者,使用类似的东西

   IF EXISTS(SELECT null FROM inserted) 
     IF EXISTS(SELECT null FROM deleted) 
       --it was an update
     ELSE
       --it was an insert
     END;
   ELSE
     --it was a delete
   END;

或者,制作 3 个单独的触发器


最后一点,你做错了这些查询 - 你声明了一堆变量(只能保存一个值)并从 inserted/deleted 中选择值,但那些伪表如果查询影响了多行(例如DELETE FROM user WHERE name = 'John'),则可以有多个行

您应该以多行方式进行操作,而不是“单行”方式:

INSERT INTO tblHist
SELECT * FROM inserted

这个可以在 hist 中插入多行,这是您应该始终考虑在 SQLServer 中做事的方式。即使您只插入一行并且您的 inserted 伪表有一个行,您必须养成将其视为“具有一个条目的行集合”的习惯,这样您编写的任何代码都不会在有一天变成“具有多个条目的行集合”时分崩离析


请注意,在您没有从 inserted 中选择的一次尝试中,您是从用户表中选择的 - 这是错误的:

当然,如果您刚刚从 tblusers 中删除了唯一的行,则 AFTER 删除触发器不会插入任何内容,但无论如何您都不应该使用 tblusers

【讨论】:

    猜你喜欢
    • 2011-08-06
    • 2011-05-19
    • 2011-03-17
    • 2014-10-02
    • 1970-01-01
    • 2023-04-07
    • 1970-01-01
    相关资源
    最近更新 更多