如何使用postgres使触发器计算会计软件中的余额

时间:2014-08-04 05:16:12

标签: sql postgresql database-design triggers

在我之前回答的问题之后,我想知道如何使用postgres触发器进行类似的余额计算。

how to calculate balances in an accounting software using postgres window function

ID   Date         In       Out    Balance
1    1/1       100.00    0.00   100.00
2    2/1        10.00    0.00   110.00
3    3/1         0.00   70.00    40.00
4    5/1         5.00    0.00    45.00
5    6/1         0.00   60.00   -15.00 

现在我需要一个"触发器"这给了我以下结果:

ID   Date         In       Out    Balance
1    1/1        100.00    0.00   100.00
2    2/1         10.00    0.00   110.00
3    3/1          0.00   70.00    40.00
6    4/1         20.00    0.00    60.00  <--- inserted new row
4    5/1          5.00    0.00    65.00
5    6/1          0.00   60.00     5.00

如何在postgres中创建触发器以更新&#34;后续&#34;行(不更新旧行)?

1 个答案:

答案 0 :(得分:4)

这实际上比它看起来更难。

多个人可能同时插入一行,并且他们的提交顺序可能与创建行的顺序不同。这意味着计算余额的触发器不会看到它应该的行,并且余额将是错误的。

要使此功能可靠,您必须LOCK TABLE ... IN EXCLUSIVE MODE才能执行INSERT,或者必须在9.2或更高版本中使用SERIALIZABLE隔离。两者都引入了重新尝试失败的交易的需要:

  • SERIALIZABLE将中止有问题的交易,并强制您重新尝试;和
  • 锁定触发器涉及锁定升级,它可能会死锁,从而导致事务中止。

因此您的应用必须准备好重新尝试失败的交易。

为了避免这种情况,您可以使用READ COMMITTED隔离,并在尝试对其进行任何其他操作之前明确LOCK TABLE ... IN EXCLUSIVE MODE感兴趣的表格,即使是{{1从它开始,如果它认为可能以后需要SELECT一行。但是,并发性会稍微有点糟糕。

所以,鉴于此,你写了类似的东西:

INSERT

然后总是:

CREATE OR REPLACE FUNCTION balance_insert_trigger() 
RETURNS trigger
LANGUAGE plpgsql
AS $$
BEGIN 
    -- This can deadlock unless you already took the lock earlier
    -- in the transaction; we put it here only for safety to make
    -- sure to block concurrent inserts.
    LOCK TABLE mytable IN EXCLUSIVE MODE;

    IF tg_op = 'INSERT' THEN
        NEW.balance = (SELECT sum(in) - sum(out) FROM mytable);
        RETURN NEW
    ELSE
        -- It's not safe to DELETE FROM or UPDATE this table because of the
        -- running balance.
        RAISE EXCEPTION '% operation not permitted on this table', tg_op;
    END IF;
END;
$$;

CREATE TRIGGER balance_insert_trigger
BEFORE INSERT OR UPDATE OR DELETE ON mytable 
FOR EACH ROW EXECUTE PROCEDURE balance_insert_trigger();

如果您也想支持BEGIN; LOCK TABLE mytable IN EXCLUSIVE MODE; INSERT INTO mytable ...; COMMIT; UPDATE,那么事情会变得令人兴奋。