返回 PostgreSQL 中更新值的变化
Posted
技术标签:
【中文标题】返回 PostgreSQL 中更新值的变化【英文标题】:Returning change in updated value in PostgreSQL 【发布时间】:2019-06-29 06:57:34 【问题描述】:假设我有两个表,payments
和 payment_events
,其中payments
包含金额,payment_events
包含金额更改的日志。
我收到通过50
更改金额的请求。我现在想同时跟踪事件和付款本身:
UPDATE payments SET amount = amount + 50 WHERE id = 1234
INSERT INTO payment_events (payment_id, changed_amount) VALUES (payment_id, 50);
到目前为止这很容易,但是如果对金额有额外的要求,例如,有max_amount
列并且金额不能超过这个值。
UPDATE payments SET amount = LEAST(max_amount, amount + 50) WHERE id = 1234;
INSERT INTO payment_events (payment_id, changed_amount) VALUES (payment_id, 50);
突然插入payment_events
可能是错误的。如果金额是180
,并且我们尝试添加50
,那么我们应该只有20
的金额变化,因为最大值是200
。当然我可以退还新的金额:
UPDATE payments SET amount = LEAST(max_amount, amount + 50)
WHERE id = 1234
RETURNING amount;
鉴于我知道以前的数量,我可以简单地区分它们。但是由于这个操作不是原子的,所以很容易出现竞争条件。
由于据我所知RETURNING
只能返回实际列,因此我不能简单地使用它来返回差异。
到目前为止,我想出的唯一解决方案是在payments
中添加一个可能命名为previous_amount
的其他无用列,然后执行以下操作:
UPDATE payments SET
previous_amount = amount,
amount = LEAST(max_amount, amount + 50)
WHERE id = 1234
RETURNING previous_amount, amount;
但这似乎有点愚蠢。有更好的方法吗?
我作为应用程序的一部分执行此操作,因此查询是从 Ruby/Sequel 应用程序执行的。
【问题讨论】:
【参考方案1】:这是一个很好的触发器应用:
CREATE FUNCTION after_update_trig() RETURNS trigger
LANGUAGE plpgsql AS
$$BEGIN
INSERT INTO payment_events (payment_id, changed_amount)
VALUES (NEW.payment_id, NEW.amount - OLD.amount);
RETURN NEW;
END;$$;
CREATE TRIGGER after_update_trig
AFTER UPDATE ON payments FOR EACH ROW
EXECUTE PROCEDURE after_update_trig();
如果这对您不可行,您必须先获取旧值:
BEGIN;
-- FOR UPDATE prevents concurrent modifications by others
SELECT * FROM payments WHERE id = 1234 FOR UPDATE;
UPDATE payments ... RETURNING *;
INSERT INTO payment_events ...;
COMMIT;
【讨论】:
在这个简化示例中这是一个很好的解决方案,在我的实际应用程序中,我有一堆其他数据要放入 payment_events 表中(例如哪个用户负责该事件),并且使用纯粹的基于触发器的解决方案似乎很难做到这一点。 那你必须走另一条路。我已经扩展了答案。以上是关于返回 PostgreSQL 中更新值的变化的主要内容,如果未能解决你的问题,请参考以下文章
具有 unnest 的 PostgreSQL 查询不返回空值的结果行
试图计算 PostgreSQL 中两个 lat-lng 点之间的距离 - PostgreSQL earth_box(ll_to_earth) 返回值的单位是啥?