返回 PostgreSQL 中更新值的变化

Posted

技术标签:

【中文标题】返回 PostgreSQL 中更新值的变化【英文标题】:Returning change in updated value in PostgreSQL 【发布时间】:2019-06-29 06:57:34 【问题描述】:

假设我有两个表,paymentspayment_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 中更新值的变化的主要内容,如果未能解决你的问题,请参考以下文章

Postgresql函数返回有变化的表

具有 unnest 的 PostgreSQL 查询不返回空值的结果行

试图计算 PostgreSQL 中两个 lat-lng 点之间的距离 - PostgreSQL earth_box(ll_to_earth) 返回值的单位是啥?

PostgreSQL如何对结果进行分组以使所有行都必须为真?

在JS中数组内部值的变化不触发视图更新的浅谈

插入后触发,更新相关模型Postgresql