假设我有两个表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
只能返回实际的列,所以我不能简单地用它来返回差异。
到目前为止,我想出的唯一解决方案是在previous_amount
上添加否则可能无用的列(可能名为payments
),并执行以下操作:
UPDATE payments SET
previous_amount = amount,
amount = LEAST(max_amount, amount + 50)
WHERE id = 1234
RETURNING previous_amount, amount;
但这似乎有点愚蠢。有更好的方法吗?
我正在作为应用程序的一部分来执行此操作,因此查询是从Ruby / Sequel应用程序执行的。
答案 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;