更新触发器后的 SQL Server

Posted

技术标签:

【中文标题】更新触发器后的 SQL Server【英文标题】:SQL Server after update trigger 【发布时间】:2014-10-23 11:31:30 【问题描述】:

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

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())

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

【问题讨论】:

只需根据主键在更新语句中添加一个连接到插入的表。 添加了一个连接,但我的触发器也会在插入时触发。这是正常行为还是什么? 【参考方案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 行,这将无法正常工作。

以上是关于更新触发器后的 SQL Server的主要内容,如果未能解决你的问题,请参考以下文章

创建触发器后的 SQL Server

SQL Server触发器根据导入时的文件名填写值

sql server 触发器一个结果集变化更新到新表

sql 检测更新触发器中修改的字段(sql server 2005)?

更新触发器 SQL Server 2008

SQL Server 触发器切换插入、删除、更新