【问题标题】:How to modify my trigger to cope with bulk update如何修改我的触发器以应对批量更新
【发布时间】:2017-07-12 22:54:25
【问题描述】:

继承了一些工作并创建了触发器。从我读过的内容来看,我做了我不应该做的事情(声明的变量等),但触发器适用于只影响一行的更新。如果更新影响多行,则会失败。

基本上,触发器将 tblMachine 中特定字段的“已删除旧/以前”值存储到 tblAudit 中。

我的触发器是...

    USE [MyDB]
GO
/****** Object:  Trigger [dbo].[tr_StatusChange_tblMachine]    Script Date: 12/07/2017 09:00:37 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER TRIGGER [dbo].[tr_StatusChange_tblMachine] ON [dbo].[tblMachine]
    AFTER UPDATE
AS BEGIN 

    SET XACT_ABORT ON;
    SET NOCOUNT ON;
    SET ROWCOUNT 0;

    DECLARE @NewStatus nvarchar(20);
    DECLARE @OldStatus nvarchar(20);
    DECLARE @ChangeMade nvarchar(50);

    IF UPDATE (MachineStatus)
        BEGIN           

            SET @NewStatus = (SELECT i.MachineStatus FROM inserted i FULL OUTER JOIN deleted d ON i.ID = d.ID);
            SET @OldStatus = (SELECT d.MachineStatus FROM deleted d FULL OUTER JOIN inserted i ON i.ID = d.ID);
            SET @ChangeMade = CONCAT ( @OldStatus, '-', @NewStatus );

            -- Status Changed
            IF @OldStatus <> @NewStatus
                BEGIN

                    INSERT INTO dbo.tblAudit (InsertedDate, Month, Year, SupportTeam, MachineName, MachineStatus, 
                    DateBuilt, DismantledDate, ServiceCheck, ServiceCheckBit, ChangeMade, ChangedBy)
                    SELECT getdate() AS InsertedDate ,(datepart(month,getdate())) AS Month, (datepart(Year,getdate())) AS Year, 
                    i.SupportTeam, i.MachineName, i.MachineStatus, i.DateBuilt, i.DismantledDate, 
                    CASE i.ServiceCheck WHEN 0 THEN 'No' ELSE 'Yes' END AS ServiceCheck, i.ServiceCheck, @ChangeMade, i.ChangedBy
                    FROM inserted i JOIN deleted d
                    ON i.MachineName = d.MachineName
                    WHERE i.SupportTeamID <> 'mbe';

                END;

            -- NO STATUS CHANGE
            IF @OldStatus = @NewStatus
                BEGIN
                    -- NO STATUS CHANGE
                    -- Service Check may have changed!

                    -- INSERT INTO AUDIT IF ServiceCheck has changed AND MachineStatus is NOT DecommissionedX or Dismantled.

                    INSERT INTO dbo.tblAudit (InsertedDate, Month, Year, SupportTeam, MachineName, MachineStatus, 
                    DateBuilt, DismantledDate, ServiceCheck, ServiceCheckBit, ChangeMade, ChangedBy)
                    SELECT getdate() AS InsertedDate, (datepart(month,getdate())) AS Month, (datepart(Year,getdate())) AS Year, 
                    i.SupportTeam, i.MachineName, i.MachineStatus, i.DateBuilt, (SELECT getdate() AS DismantledDate), 
                    CASE i.ServiceCheck WHEN 0 THEN 'No' ELSE 'Yes' END AS ServiceCheck, i.ServiceCheck, 'SC Changed - Status Not Changed', i.ChangedBy
                    FROM inserted i JOIN deleted d
                    ON i.MachineName = d.MachineName
                    WHERE i.SupportTeamID <> 'mbe' And i.ServiceCheck <> d.ServiceCheck 
                    AND (i.MachineStatus <> 'DecommissionedX' AND i.MachineStatus <> 'Dismantled');

                END;

        END;

END

从我的搜索中,我发现触发器每条语句触发一次,而不是每行受影响。我读过可以修改触发器以检查正在更新的行数,然后使用此信息根据每一行执行操作。不幸的是,我还没有找到一种我理解的方法。

我的强项是 Web 前端编码,而我的 SQLServer 编码有限。代码需要在 SQL 中,而不是在前端,因为除了 Web 前端之外,还有一些其他工具可以更新这些记录。

任何帮助调整此触发器以便它也可以在一个语句中处理多个更新将不胜感激。

【问题讨论】:

  • 您的搜索应该已经出现了大量示例。您是否有关于他们的更具体、更集中的问题,听起来不像是在试图让某人免费为您工作?
  • 我的搜索完成了,但我理解的程度不足以实现。有些人正在使用我读过的光标可以达到性能。
  • SET XACT_ABORT 开启了怎么办?如果出现错误,这将取消事务中的所有剩余语句。我没有看到事务或错误处理。
  • @benjaminmoskovits 这证明我有一些 SQL 知识。这来自我找到并改编的基本代码...我会研究这个并删除或添加一些错误处理...谢谢
  • 这需要一点时间,但通过 Sommarskog 的错误处理和事务处理工作确实值得。 sommarskog.se/error_handling/Part1.html。您将更多地了解 TSQL,并更接近成为一名真正的专业人士。

标签: sql-server triggers


【解决方案1】:

这可能不是最好的选择,但解决这个问题的一种方法是使用临时表来存储更改的机器,然后加入到插入或删除的表中。如:

ALTER TRIGGER [dbo].[tr_StatusChange_tblMachine] ON [dbo].[tblMachine]
AFTER UPDATE
AS BEGIN 

SET XACT_ABORT ON;
SET NOCOUNT ON;
SET ROWCOUNT 0;

CREATE TABLE #tmp(MachineName nvarchar(50), NewStatus nvarchar(20) NULL, OldStatus nvarchar(20) NULL)

INSERT INTO #tmp(MachineName, NewStatus) 
   SELECT MachineName,MachineStatus from inserted

UPDATE #tmp SET OldStatus = d.MachineStatus
 From deleted d 
 INNER JOIN #tmp t on t.MachineName = d.MachineName

---You could delete from #tmp where OldStatus is null to ensure this was an update


        -- Status Changed
                INSERT INTO dbo.tblAudit (InsertedDate, Month, Year, SupportTeam, MachineName, MachineStatus, 
                DateBuilt, DismantledDate, ServiceCheck, ServiceCheckBit, ChangeMade, ChangedBy)
                SELECT getdate() AS InsertedDate ,(datepart(month,getdate())) AS Month, (datepart(Year,getdate())) AS Year, 
                i.SupportTeam, i.MachineName, i.MachineStatus, i.DateBuilt, i.DismantledDate, 
                CASE i.ServiceCheck WHEN 0 THEN 'No' ELSE 'Yes' END AS ServiceCheck, i.ServiceCheck, @ChangeMade, i.ChangedBy
                FROM inserted i JOIN #tmp t
                ON i.MachineName = t.MachineName and t.NewStatus <> t.OldStatus
                WHERE i.SupportTeamID <> 'mbe' ;

                -- NO STATUS CHANGE
                -- Service Check may have changed!

                -- INSERT INTO AUDIT IF ServiceCheck has changed AND MachineStatus is NOT DecommissionedX or Dismantled.

                INSERT INTO dbo.tblAudit (InsertedDate, Month, Year, SupportTeam, MachineName, MachineStatus, 
                DateBuilt, DismantledDate, ServiceCheck, ServiceCheckBit, ChangeMade, ChangedBy)
                SELECT getdate() AS InsertedDate, (datepart(month,getdate())) AS Month, (datepart(Year,getdate())) AS Year, 
                i.SupportTeam, i.MachineName, i.MachineStatus, i.DateBuilt, (SELECT getdate() AS DismantledDate), 
                CASE i.ServiceCheck WHEN 0 THEN 'No' ELSE 'Yes' END AS ServiceCheck, i.ServiceCheck, 'SC Changed - Status Not Changed', i.ChangedBy
                FROM inserted i JOIN #tmp t
                ON i.MachineName = t.MachineName and t.OldStatus = t.NewStatus
                WHERE i.SupportTeamID <> 'mbe' And i.ServiceCheck <> d.ServiceCheck 
                AND (i.MachineStatus <> 'DecommissionedX' AND i.MachineStatus <> 'Dismantled');

 END

再次,您需要检查语法,因为我只是在修改您的代码,但这是这个想法的要点。

【讨论】:

  • 谢谢..我可以看到您对此的想法...我需要删除表#tmp吗?或者我应该在触发器之外创建表,然后在每个触发器结束时进行 trucate?还是#tmp 是一个自我清除的虚拟表?
  • 你应该把它放在触发器的末尾。
  • 太棒了...我会试一试,但仍然试图弄清楚为什么使用似乎是虚拟表的#tmp 表与使用插入/删除的表有什么不同这也是虚拟表...试一试!
【解决方案2】:

我认为这样做。诀窍是直接使用插入和删除表中的 MachineStatus 插入到审计表中,而不是尝试预先计算通用的新旧状态。

USE [MyDB]
GO
/****** Object:  Trigger [dbo].[tr_StatusChange_tblMachine]    Script Date: 12/07/2017 09:00:37 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER TRIGGER [dbo].[tr_StatusChange_tblMachine] ON [dbo].[tblMachine]
    AFTER UPDATE
AS BEGIN 

    SET XACT_ABORT ON;
    SET NOCOUNT ON;
    SET ROWCOUNT 0;

    IF UPDATE (MachineStatus)
        BEGIN           

            INSERT INTO dbo.tblAudit (InsertedDate, Month, Year, SupportTeam, MachineName, MachineStatus, 
            DateBuilt, DismantledDate, ServiceCheck, ServiceCheckBit, ChangeMade, ChangedBy)
            SELECT getdate() AS InsertedDate ,(datepart(month,getdate())) AS Month, (datepart(Year,getdate())) AS Year, 
            i.SupportTeam, i.MachineName, i.MachineStatus, i.DateBuilt, i.DismantledDate, 
            CASE i.ServiceCheck WHEN 0 THEN 'No' ELSE 'Yes' END AS ServiceCheck, i.ServiceCheck, CONCAT(d.MachineStatus, '-', i.MachineStatus), i.ChangedBy
            FROM inserted i JOIN deleted d
            ON i.MachineName = d.MachineName
            WHERE i.SupportTeamID <> 'mbe'
            -- Status Changed
            AND i.MachineStatus <> d.MachineStatus;


            -- NO STATUS CHANGE
            -- Service Check may have changed!

            -- INSERT INTO AUDIT IF ServiceCheck has changed AND MachineStatus is NOT DecommissionedX or Dismantled.

            INSERT INTO dbo.tblAudit (InsertedDate, Month, Year, SupportTeam, MachineName, MachineStatus, 
            DateBuilt, DismantledDate, ServiceCheck, ServiceCheckBit, ChangeMade, ChangedBy)
            SELECT getdate() AS InsertedDate, (datepart(month,getdate())) AS Month, (datepart(Year,getdate())) AS Year, 
            i.SupportTeam, i.MachineName, i.MachineStatus, i.DateBuilt, (SELECT getdate() AS DismantledDate), 
            CASE i.ServiceCheck WHEN 0 THEN 'No' ELSE 'Yes' END AS ServiceCheck, i.ServiceCheck, 'SC Changed - Status Not Changed', i.ChangedBy
            FROM inserted i JOIN deleted d
            ON i.MachineName = d.MachineName
            WHERE i.SupportTeamID <> 'mbe' And i.ServiceCheck <> d.ServiceCheck 
            AND (i.MachineStatus <> 'DecommissionedX' AND i.MachineStatus <> 'Dismantled')
            -- NO STATUS CHANGE
            AND (i.MachineStatus = d.MachineStatus);


        END;

END

【讨论】:

  • 好,但没有雪茄...对我的代码进行了改进,但同样,这仅在更新语句影响单行时才有效。据我所知,[@NewStatus]、[@OldStatus] 和 [@ChangeMade] 一次不能有多个值……在只有一台机器更新的更新中,这是可行的。尽管机器更新为一种状态,但它们的 [@OldStatus] 可能具有不同的值。
  • 你是对的。但是,如果您检查代码,您会发现这些变量在我更改后不再使用。我只是忘了从你的代码中删除它们。
猜你喜欢
  • 2013-09-06
  • 2015-04-04
  • 1970-01-01
  • 2013-11-01
  • 1970-01-01
  • 2021-04-24
  • 2015-11-26
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多