使用 "AFTER INSERT, UPDATE, DELETE" DML 触发器。如何引用触发行?

Using "AFTER INSERT, UPDATE, DELETE" DML trigger. How reference triggering row?

我有一个 Main table,我想在其中对 Sub table 中的行的值求和。一个或多个子行将引用单个主行。我希望 Main.Total 是所有相应 Sub.Amount 值的总和。我正在尝试实现一个 AFTER INSERT、UPDATE、DELETE 触发器来完成此操作。 除了触发器之外,我对其他解决方案持开放态度,例如使用视图,如果这是解决此问题的更好方法。即使有更好的解决方案,我仍然有兴趣学习如何使用触发器解决这个问题,即使只是出于学术目的。这是一个简化的例子:

CREATE DATABASE TEST;
GO
USE TEST;
GO

CREATE TABLE Main (
    Id INT
    ,Total INT DEFAULT(0)
    );
GO

CREATE TABLE Sub (
    Main_fk INT
    ,Sub_Id INT
    ,Amount INT
    );
GO

CREATE TRIGGER Update_Main_Total
    ON Sub
    AFTER INSERT, UPDATE, DELETE
    AS BEGIN
        DECLARE @Main_Id INT = (
            --  The Main_fk value for the inserted/updated/deleted
            --  Sub row that caused the trigger to fire
            );
        UPDATE Main
        SET Total = (
            SELECT SUM(Amount)
            FROM Sub
            WHERE Main_fk = @Main_Id
            )
        WHERE Id = @Main_Id;
    END;
GO

我能够很简单地弄明白。感谢@Dale_Burrell 在评论中的指导。我发现这个 link 是对我想要的信息的更直接的回答:https://www.mssqltips.com/sqlservertip/2342/understanding-sql-server-inserted-and-deleted-tables-for-dml-triggers/

简而言之,我需要引用内置的 inserteddeleted 表来引用触发行。我的问题立即 "But what about an Update?"。一个 Update 将利用这两个表(考虑一下,或使用上面的 link 进行解释)。这是更正后的触发器:

CREATE TRIGGER Update_Main_Total
    ON Sub
    AFTER INSERT, UPDATE, DELETE
    AS BEGIN
        DECLARE @main_id INT;

        IF EXISTS (SELECT * FROM inserted)
            SELECT @main_id = Main_fk
            FROM inserted;

        ELSE
            SELECT @main_id = Main_fk
            FROM deleted;

        UPDATE Main
        SET Total = (
            SELECT SUM(Amount)
            FROM Sub
            WHERE Main_fk = @Main_Id
            )
        WHERE Id = @Main_Id;
    END;
GO

SQL 服务器中没有 for-each-row 触发器。相反,在触发器中,您可以访问 inserteddeleted 伪 table。 inserted 包含为更新插入的行或行的更改版本,以及 deleted 删除的行或更新前行的版本。

因此您需要使用类似下面的内容来更新总和。它首先获取 inserted 的每个 ID 和 deleted 的模拟值的总和,然后对结果进行完全连接——这里需要一个完全连接,因为并非所有 ID 都必须在两个集合中——然后更新 main.

UPDATE m
       SET m.total = m.total + z.total
       FROM main m
            INNER JOIN (SELECT coalesce(x.main_fk, y.main_fk) main_fk,
                               coalesce(x.total, 0) - coalesce(y.total, 0) total
                               FROM (SELECT i.main_fk,
                                            sum(i.amount) total
                                            FROM inserted i
                                            GROUP BY i.main_fk) x
                                    FULL JOIN (SELECT d.main_fk,
                                                      sum(d.amount) total
                                                      FROM delete d
                                                      GROUP BY d.main_fk) y
                                              ON y.main_fk = x.main_fk) z
                       ON z.main_fk = m.id;

(如果没有任何 sub 记录,您可能想将 main 中的 total 设置为 NULL。上面的查询不起作用那,那需要一些额外的工作。)

但是物理存储这些可以从其他人计算的数字存在不一致的风险。如果触发器在一段时间内因其他原因被禁用或无法(正确)工作,则对 sub table 的更改不会(正确)反映在 main table。你会在那里有假数字,甚至可能不会认出它。

我会避免这样的事情,如果可能的话,我会选择一个视图。 (写起来也更容易。;))

CREATE VIEW main_with_total
AS
SELECT m.id,
       sum(s.amount) total
       FROM main m
            LEFT JOIN sub s
                      ON s.main_fk = m.id
       GROUP BY m.id;

(在这里,对于不存在 sub 记录的 ID,您会得到 NULL 作为 total。如果您想要 0,则将 total 的表达式更改为coalesce(sum(s.amount), 0).)