【问题标题】:Trigger to update balance after a transaction amount record has been changed更改交易金额记录后触发更新余额
【发布时间】:2022-01-11 23:58:58
【问题描述】:

表格账户和交易

Account = {accNumber, balance, ...}
Transaction = {accNumber, amount, transNumber, ...}

在哪里Transaction.accNumber references Account.accNumber

我已经成功创建了INSERT之后更新余额的触发器

CREATE TRIGGER TR_Account_Balance
ON Transactions AFTER INSERT
AS
BEGIN
    UPDATE account SET
        balance = ins.newBalance
    FROM (
        SELECT a.accnumber,a.balance + SUM(i.amount) AS newBalance
        FROM Account a
        INNER JOIN inserted i ON i.accNumber = a.accNumber
        GROUP BY a.accNumber, a.balance
    ) AS ins
    WHERE account.accnumber = ins.accnumber
END

现在我需要创建一个触发器,它会根据交易 AFTER UPDATE 相应地更改余额。

例子:

   |accNumber | balance | ...
   |-----------------------------
   |        1 |   100   | ...

   |accNumber | amount | ...
   |-----------------------------
   |        1 |   20   | ...

如果我们将amount 更新为 10,balance 应该更改为 90。 我怎么可能这样做?

【问题讨论】:

  • AFTER INSERT, UPDATE 添加到您的定义中?
  • 视图(如果需要索引)会自动执行此操作 - 为什么要为复杂性烦恼?
  • @DaleK AFTER UPDATE 仍然会增加余额,即使金额减少。我想我必须将金额变化添加到余额中,前提是我(目前)知道如何做到这一点。
  • @SMor 恐怕这只是我任务的一部分。
  • 如果您需要逻辑方面的帮助,您需要提供示例数据和所需的结果。

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


【解决方案1】:

您应该以规范化的方式真正做到这一点,通过使用视图。为了获得更好的性能,您可以对其进行索引。

索引视图受到一些限制,特别是:

  • 没有外部联接或应用
  • 必须是模式绑定的
  • 分组视图需要COUNT_BIG,并且只能使用SUM作为另一个聚合
CREATE VIEW dbo.vAccountBalance
WITH SCHEMABINDING AS

SELECT
  tr.accnumber,
  SUM(tr.amount) AS balance,
  COUNT_BIG(*) AS numTransactions -- this MUST be added
FROM dbo.Transactions tr;  -- schema-qualify

GO
CREATE UNIQUE CLUSTERED INDEX CX_vAccountBalance ON dbo.vAccountBalance (accnumber);

在任何插入、更新或删除期间,服务器将与其他索引一起维护此索引。


如果您真的想在触发器中执行此操作,您可以使用以下

  • 注意Account 表是如何只被引用一次,并添加差异,而不是再次自连接
  • 注意inserteddeleted 是如何通过主键连接在一起的,并求和
CREATE TRIGGER TR_Account_Balance
ON Transactions AFTER INSERT, UPDATE, DELETE
AS

SET NOCOUNT ON;

IF NOT EXISTS (SELECT 1 FROM inserted) AND NOT EXISTS (SELECT 1 FROM deleted)
    RETURN;  -- early bail-out

UPDATE a  -- update the already referenced Account table
SET
    balance += ins.diffBalance
FROM Account a
INNER JOIN (
    SELECT
      i.accnumber,
      SUM(i.amount) AS diffBalance
    FROM (
        SELECT i.transNumber, i.accnumber, i.amount
        FROM inserted i
    )
    FULL JOIN (
        SELECT d.transNumber, d.accnumber, -(d.amount)
        FROM deleted d
    ) ON i.transNumber = a.transNumber
    GROUP BY i.accNumber
) AS ins ON a.accnumber = ins.accnumber;

GO

您也可以将其拆分为单独的 INSERT UPDATEDELETE 触发器,在这种情况下,您可以删除前者的 deleted 部分,删除后者的 inserted 部分,然后更改UPDATE 使用 INNER JOIN 而不是 FULL JOIN

【讨论】:

    【解决方案2】:

    如果您通过 proc 执行插入更新删除,这将是更新映射表或其他表的最佳位置。 如果您仍想在触发器中执行此操作(请仔细),请在同一表级别计算您的 SUM 并更新主表上的余额,以便它也涵盖更新和删除。

    架构:

    DROP TABLE IF EXISTS dbo.AccountTransaction
    DROP TABLE IF EXISTS dbo.Account
    
    CREATE TABLE dbo.Account
    (
        AccountNumber INT            CONSTRAINT PK_AccountId PRIMARY KEY CLUSTERED IDENTITY(1, 1) NOT NULL,
        Balance       DECIMAL(18, 9) CONSTRAINT DF_Account_Balance DEFAULT 0.0 NOT NULL
    )
    GO
    
    INSERT INTO dbo.Account
    (
        Balance
    )
    VALUES
    (
        DEFAULT -- decimal(18, 9)
    )
    
    CREATE TABLE dbo.AccountTransaction
    (
        AccountTransactionId INT            CONSTRAINT PK_AccountTransactionId PRIMARY KEY CLUSTERED IDENTITY(1, 1) NOT NULL,
        AccountNumber        INT            CONSTRAINT FK_AccountTransaction_Account FOREIGN KEY REFERENCES dbo.Account (AccountNumber) NOT NULL,
        Amount               DECIMAL(18, 9) CONSTRAINT DF_AccountTransaction_Amount DEFAULT 0.0 NOT NULL
    )
    GO
    
    CREATE TRIGGER dbo.tr_AccountTransaction
    ON dbo.AccountTransaction
    FOR INSERT, UPDATE, DELETE
    AS
    BEGIN
        SET NOCOUNT ON;
    
        DECLARE @Inserted AS INT =
                (
                    SELECT COUNT (1)
                    FROM   INSERTED
                )
        DECLARE @Deleted AS INT =
                (
                    SELECT COUNT (1)
                    FROM   DELETED
                )
    
        IF @Inserted > 0
        BEGIN
            UPDATE dbo.Account
            SET    Balance = x.NewBalance
            FROM
                   (
                       SELECT   SUM (at.Amount) AS NewBalance
                       FROM     Inserted AS i
                       JOIN     dbo.AccountTransaction AS at
                           ON   at.AccountNumber = i.AccountNumber
                       GROUP BY i.AccountNumber
                   ) AS x
        END
    
        IF @Inserted = 0
           AND @Deleted > 0
        BEGIN
            UPDATE dbo.Account
            SET    Balance = x.NewBalance
            FROM
                   (
                       SELECT   SUM (at.Amount) AS NewBalance
                       FROM     Deleted AS d
                       JOIN     dbo.AccountTransaction AS at
                           ON   at.AccountNumber = d.AccountNumber
                       GROUP BY d.AccountNumber
                   ) AS x
        END
    END
    GO
    

    ** 调试**

    INSERT INTO dbo.AccountTransaction
    (
        AccountNumber,
        Amount
    )
    SELECT a.AccountNumber,
           12.0
    FROM   dbo.Account AS a
    
    SELECT a.AccountNumber,
           a.Balance
    FROM   dbo.Account AS a
    
    UPDATE at
    SET    at.Amount += 30
    FROM   dbo.AccountTransaction AS at
    WHERE  at.AccountTransactionId = 1
    
    SELECT a.AccountNumber,
           a.Balance
    FROM   dbo.Account AS a
     
    SELECT at.AccountTransactionId,
           at.AccountNumber,
           at.Amount
    FROM   dbo.AccountTransaction AS at
    
    UPDATE at
    SET    at.Amount -= 20
    FROM   dbo.AccountTransaction AS at
    WHERE  at.AccountTransactionId = 1
    
    SELECT a.AccountNumber,
           a.Balance
    FROM   dbo.Account AS a
     
    SELECT at.AccountTransactionId,
           at.AccountNumber,
           at.Amount
    FROM   dbo.AccountTransaction AS at
    DELETE a
    FROM   dbo.AccountTransaction AS a
    WHERE  a.AccountTransactionId = 2
    
    SELECT a.AccountNumber,
           a.Balance
    FROM   dbo.Account AS a
    
    SELECT at.AccountTransactionId,
           at.AccountNumber,
           at.Amount
    FROM   dbo.AccountTransaction AS at
    

    【讨论】:

    • 这会在删除或更新时出错,您需要引用deleted 来检查差异,并且您需要Balance += x.Difference
    • 而你唯一的(也是微不足道的)更新测试增加了余额。尝试减少余额的更改。
    • 是的,我试图简化我的查询并粘贴了错误的版本。感谢您发现这一点。 @查理脸
    • 仍然错误:对于更新,您需要同时比较 inserteddeleted,您还需要考虑差异。在这个阶段,单独的触发器可能更明智
    • 我也有这个并删除了。我们为审计触发器做了很多,但我猜我们不需要的要求。如果有插入 [不管删除] 正在计算该帐户所有行的总和。所以它应该捕获所需的价值。如果您看到我的逻辑,您可以看到我将插入和删除的临时表纯粹用于帐号映射。总和基于它在主表上找到的所有行,而不是插入或删除的记录
    猜你喜欢
    • 1970-01-01
    • 2013-10-21
    • 1970-01-01
    • 1970-01-01
    • 2015-11-09
    • 1970-01-01
    • 1970-01-01
    • 2021-08-28
    • 2019-03-28
    相关资源
    最近更新 更多