在我之前回答的问题之后,我想知道如何使用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;行(不更新旧行)?
答案 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
,那么事情会变得令人兴奋。