我有一个主表,我想在其中汇总子表中各行的值。一个或多个子行将引用一个主行。我希望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
答案 0 :(得分:2)
SQL Server中没有for-each-row触发器。相反,您可以在触发器中访问inserted
和deleted
伪表。 inserted
包含要插入的行或行的更改版本,以及deleted
删除的行或更新前的行版本。
因此,您需要使用类似以下的内容来更新总和。它首先获取inserted
的每个ID的总和,并deleted
的模拟的每个ID的总和,然后对结果进行完全连接-这里需要完全连接,因为并非所有ID都在这两个集合中,所以需要完全连接-然后更新{{ 1}}。
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;
条记录,您可能想将total
中的main
设置为NULL
。上面的查询不会这样做,这需要一些额外的工作。)
但是,物理地存储可以从其他人那里计算出的数字会带来不一致的风险。如果触发器在一段时间内被禁用或由于其他原因(适当)无法正常工作,则sub
表中的更改不会(正确)反映在sub
表中。您那里可能有虚假数字,甚至可能无法识别。
如果可能的话,我会避免这样的事情,而是选择一个视图。 (写起来也更容易。))
main
(这里您将获得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;
作为NULL
的ID,其中不存在任何total
记录。如果要0,请将sub
的表达式更改为{{ 1}}。)
答案 1 :(得分:-2)
我很容易就能弄清楚。感谢@Dale_Burrell在注释中的指导。我发现此链接是我想要的信息的更直接答案:https://www.mssqltips.com/sqlservertip/2342/understanding-sql-server-inserted-and-deleted-tables-for-dml-triggers/
简而言之,我需要引用内置的inserted
和deleted
表来引用触发行。我的问题是立即“但是更新呢?”。更新将使用这两个表(考虑一下,或使用上面的链接进行解释)。这是更正后的触发器:
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