【问题标题】:SQL Server after update trigger更新触发器后的 SQL Server
【发布时间】:2024-05-23 18:10:02
【问题描述】:

这个触发器有问题。我希望它更新请求的信息 只针对有问题的行(我刚刚更新的那一行)而不是整个表。

CREATE TRIGGER [dbo].[after_update] 
    ON [dbo].[MYTABLE]
    AFTER UPDATE
    AS 
    BEGIN
          UPDATE MYTABLE 
          SET mytable.CHANGED_ON = GETDATE(),
          CHANGED_BY=USER_NAME(USER_ID())

如何告诉触发器这仅适用于相关行?

【问题讨论】:

  • 只需根据主键在更新语句中添加一个连接到插入的表。
  • 添加了一个连接,但我的触发器也会在插入时触发。这是正常行为还是什么?

标签: sql sql-server tsql sql-server-express sql-server-2014


【解决方案1】:

这是我测试后的例子

CREATE TRIGGER [dbo].UpdateTasadoresName 
ON [dbo].Tasadores  
FOR  UPDATE
AS 
      UPDATE Tasadores 
      SET NombreCompleto = RTRIM( Tasadores.Nombre + ' ' + isnull(Tasadores.ApellidoPaterno,'') + ' ' + isnull(Tasadores.ApellidoMaterno,'')    )  
      FROM Tasadores 
    INNER JOIN INSERTED i ON Tasadores.id = i.id

插入的特殊表将包含更新记录中的信息。

【讨论】:

    【解决方案2】:

    试试这个(更新,不是更新后)

    CREATE TRIGGER [dbo].[xxx_update] ON [dbo].[MYTABLE]
        FOR UPDATE
        AS
        BEGIN
    
            UPDATE MYTABLE
            SET mytable.CHANGED_ON = GETDATE()
                ,CHANGED_BY = USER_NAME(USER_ID())
            FROM inserted
            WHERE MYTABLE.ID = inserted.ID
    
        END
    

    【讨论】:

    • FOR UPDATE 和 AFTER UPDATE 是相同的,它们都在更新发生并提交后触发。正如您正确指出的那样,插入的表包含所有修改的记录。
    • 触发触发器时尚未提交更新,@kuklei。触发器可以阻止提交更新。
    • 糟糕!我的错。措辞选择错误。 @AndrewR 是正确的。
    【解决方案3】:

    这样做很简单, 首先创建您想要保留日志的表的副本 例如,您有 Table dbo.SalesOrder,其中包含 SalesOrderId、FirstName、LastName、LastModified 列

    您的版本存档表应该是 dbo.SalesOrderVersionArchieve,其中包含 SalesOrderVersionArhieveId、SalesOrderId、FirstName、LastName、LastModified 列

    这是您将如何在 SalesOrder 表上设置触发器

    USE [YOURDB]
    GO
    SET ANSI_NULLS ON
    GO
    SET QUOTED_IDENTIFIER ON
    GO
    -- =============================================
    -- Author:      Karan Dhanu
    -- Create date: <Create Date,,>
    -- Description: <Description,,>
    -- =============================================
    CREATE TRIGGER dbo.[CreateVersionArchiveRow]
       ON  dbo.[SalesOrder]
      AFTER Update
    AS 
    BEGIN
    
        SET NOCOUNT ON;
    INSERT INTO dbo.SalesOrderVersionArchive
    
       SELECT *
       FROM deleted;
    
    END
    

    现在,如果您在 saleOrder 表中进行任何更改,它将显示 VersionArchieve 表中的更改

    【讨论】:

      【解决方案4】:

      您应该能够访问INSERTED 表并检索 ID 或表的主键。类似于这个例子的东西......

      CREATE TRIGGER [dbo].[after_update] ON [dbo].[MYTABLE]
      AFTER UPDATE AS 
      BEGIN
          DECLARE @id AS INT
          SELECT @id = [IdColumnName]
          FROM INSERTED
      
          UPDATE MYTABLE 
          SET mytable.CHANGED_ON = GETDATE(),
          CHANGED_BY=USER_NAME(USER_ID())
          WHERE [IdColumnName] = @id
      

      这是 MSDN 上使用触发器时可用的 INSERTEDDELETED 表上的链接:http://msdn.microsoft.com/en-au/library/ms191300.aspx

      【讨论】:

      • Kane,如果更新多行,这是不正确的,因为标量值只会为集合中的最后一行设置,而错过所有其他行。
      • @SeanGallardy,嗯,是的,你是对的。我曾假设只有 1 行正在更新。你知道他们对假设的看法。
      • 使用触发器,您应该始终假设有多个行受到影响。
      【解决方案5】:

      试试这个解决方案。

             DECLARE @Id INT
             DECLARE @field VARCHAR(50)
      
             SELECT @Id= INSERTED.CustomerId       
             FROM INSERTED
      
             IF UPDATE(Name)
             BEGIN
                    SET @field = 'Updated Name'
             END
      
             IF UPDATE(Country)
             BEGIN
                    SET @field = 'Updated Country'
             END
      
             INSERT INTO CustomerLogs
             VALUES(@Id, @field)
      
             // OR
             -- If you wish to update existing table records.
             UPDATE YOUR_TABLE SET [FIELD]=[VALUE] WHERE {CONDITION}
      

      我没有使用旧版本的 sql server 进行检查,但这将适用于 sql server 2012。

      【讨论】:

      • 不是很健壮,因为它假定只会更新一行。
      • @DaleBurrell 如果要更新多行,可以添加带有触发器的更新语句。
      • 最佳实践是假设多行并相应编码。对于不熟悉触发器的人来说,这不是一个好例子,因为它教会了他们坏习惯。
      【解决方案6】:

      您可以调用INSERTED,SQL Server 使用这些表来捕获事件发生前后修改行的数据。我假设在您的表中键的名称是Id

      我觉得下面的代码可以帮到你

      CREATE TRIGGER [dbo].[after_update]
      ON [dbo].[MYTABLE]
         AFTER UPDATE
      AS
      BEGIN
          UPDATE dbo.[MYTABLE]
          SET    dbo.[MYTABLE].CHANGED_ON = GETDATE(),
                 dbo.[MYTABLE].CHANGED_BY = USER_NAME(USER_ID())
          FROM   INSERTED
          WHERE  INSERTED.Id = dbo.[MYTABLE].[Id]
      END
      

      【讨论】:

        【解决方案7】:

        尝试使用此脚本创建一个临时表 TESTTEST,并观察触发器按以下顺序调用的优先顺序:1) INSTEAD OF,2) FOR,3) AFTER

        所有逻辑都放在 INSTEAD OF 触发器中,我有 2 个示例说明您如何编写某些场景...

        祝你好运……

        CREATE TABLE TESTTEST
        (
            ID  INT,
            Modified0 DATETIME,
            Modified1 DATETIME
        )
        GO
        CREATE TRIGGER [dbo].[tr_TESTTEST_0] ON [dbo].TESTTEST
        INSTEAD OF INSERT,UPDATE,DELETE
        AS
        BEGIN
            SELECT 'INSTEAD OF'
            SELECT 'TT0.0'
            SELECT * FROM TESTTEST
        
            SELECT *, 'I' Mode
            INTO #work
            FROM INSERTED
        
            UPDATE #work SET Mode='U' WHERE ID IN (SELECT ID FROM DELETED)
            INSERT INTO #work (ID, Modified0, Modified1, Mode)
            SELECT ID, Modified0, Modified1, 'D'
            FROM DELETED WHERE ID NOT IN (SELECT ID FROM INSERTED)
        
            --Check Security or any other logic to add and remove from #work before processing
            DELETE FROM #work WHERE ID=9 -- because you don't want anyone to edit this id?!?!
            DELETE FROM #work WHERE Mode='D' -- because you don't want anyone to delete any records
        
            SELECT 'EV'
            SELECT * FROM #work
        
            IF(EXISTS(SELECT TOP 1 * FROM #work WHERE Mode='I'))
            BEGIN
                SELECT 'I0.0'
                INSERT INTO dbo.TESTTEST (ID, Modified0, Modified1)
                SELECT ID, Modified0, Modified1
                FROM #work
                WHERE Mode='I'
                SELECT 'Cool stuff would happen here if you had FOR INSERT or AFTER INSERT triggers.'
                SELECT 'I0.1'
            END
        
            IF(EXISTS(SELECT TOP 1 * FROM #work WHERE Mode='D'))
            BEGIN
                SELECT 'D0.0'
                DELETE FROM TESTTEST WHERE ID IN (SELECT ID FROM #work WHERE Mode='D')
                SELECT 'Cool stuff would happen here if you had FOR DELETE or AFTER DELETE triggers.'
                SELECT 'D0.1'
            END
        
            IF(EXISTS(SELECT TOP 1 * FROM #work WHERE Mode='U'))
            BEGIN
                SELECT 'U0.0'
                UPDATE t SET t.Modified0=e.Modified0, t.Modified1=e.Modified1
                FROM dbo.TESTTEST t
                INNER JOIN #work e ON e.ID = t.ID
                WHERE e.Mode='U'
                SELECT 'U0.1'
            END
            DROP TABLE #work
        
            SELECT 'TT0.1'
            SELECT * FROM TESTTEST
        END
        GO
        CREATE TRIGGER [dbo].[tr_TESTTEST_1] ON [dbo].TESTTEST
        FOR UPDATE
        AS
        BEGIN
            SELECT 'FOR UPDATE'
        
            SELECT 'TT1.0'
            SELECT * FROM TESTTEST
        
            SELECT 'I1'
            SELECT * FROM INSERTED
        
            SELECT 'D1'
            SELECT * FROM DELETED
        
            SELECT 'TT1.1'
            SELECT * FROM TESTTEST
        END
        GO
        CREATE TRIGGER [dbo].[tr_TESTTEST_2] ON [dbo].TESTTEST
        AFTER UPDATE
        AS
        BEGIN
            SELECT 'AFTER UPDATE'
        
            SELECT 'TT2.0'
            SELECT * FROM TESTTEST
        
            SELECT 'I2'
            SELECT * FROM INSERTED
        
            SELECT 'D2'
            SELECT * FROM DELETED
        
            SELECT 'TT2.1'
            SELECT * FROM TESTTEST
        END
        GO
        
        SELECT 'Start'
        INSERT INTO TESTTEST (ID, Modified0) VALUES (9, GETDATE())-- not going to insert
        SELECT 'RESTART'
        INSERT INTO TESTTEST (ID, Modified0) VALUES (10, GETDATE())--going to insert
        SELECT 'RESTART'
        UPDATE TESTTEST SET Modified1=GETDATE() WHERE ID=10-- gointo to update
        SELECT 'RESTART'
        DELETE FROM TESTTEST WHERE ID=10-- not going to DELETE
        SELECT 'FINISHED'
        
        SELECT * FROM TESTTEST
        DROP TABLE TESTTEST
        

        【讨论】:

          【解决方案8】:

          首先,您已经看到的触发器将更新表中的每条记录。没有进行过滤来完成更改的行。

          其次,您假设批次中只有一行更改,这是不正确的,因为多行可能会更改。

          正确执行此操作的方法是使用虚拟插入和删除表:http://msdn.microsoft.com/en-us/library/ms191300.aspx

          【讨论】:

          • 添加:FROM dbo.mytable INNER JOIN 插入 ON mytable.TW_ID = inserted.TW_ID 为什么在插入事件后触发器也会触发?
          【解决方案9】:
          CREATE TRIGGER [dbo].[after_update] ON [dbo].[MYTABLE]
          AFTER UPDATE
          AS
          BEGIN
              DECLARE @ID INT
          
              SELECT @ID = D.ID
              FROM inserted D
          
              UPDATE MYTABLE
              SET mytable.CHANGED_ON = GETDATE()
                  ,CHANGED_BY = USER_NAME(USER_ID())
              WHERE ID = @ID
          END
          

          【讨论】:

          • 这种类型的触发器不会处理多行操作。如果更新超过 1 行,这将无法正常工作。