在PostgreSQL中返回更新值的更改

时间:2019-02-05 12:43:02

标签: postgresql audit

假设我有两个表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只能返回实际的列,所以我不能简单地用它来返回差异。

到目前为止,我想出的唯一解决方案是在previous_amount上添加否则可能无用的列(可能名为payments),并执行以下操作:

UPDATE payments SET
  previous_amount = amount,
  amount = LEAST(max_amount, amount + 50)
WHERE id = 1234
RETURNING previous_amount, amount;

但这似乎有点愚蠢。有更好的方法吗?

我正在作为应用程序的一部分来执行此操作,因此查询是从Ruby / Sequel应用程序执行的。

1 个答案:

答案 0 :(得分:3)

这是触发器的一个很好的应用程序:

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;