Suppose that I have two tables, payments and payment_events where payments contains an amount, and payment_events contains a log of changes to the amount.
I receive a request to change the amount by 50. I now want to track both the event, and the payment itself:
UPDATE payments SET amount = amount + 50 WHERE id = 1234
INSERT INTO payment_events (payment_id, changed_amount) VALUES (payment_id, 50);
So far this is easy, but what if there is an additional requirement on the amount, for example, there is max_amount column and amount mustn't exceed this value.
UPDATE payments SET amount = LEAST(max_amount, amount + 50) WHERE id = 1234;
INSERT INTO payment_events (payment_id, changed_amount) VALUES (payment_id, 50);
Suddenly the insert into payment_events could be wrong. If amount is 180 and we tried to add 50 then we should only have an amount change of 20, since the max is 200. Of course I could return the new amount:
UPDATE payments SET amount = LEAST(max_amount, amount + 50)
WHERE id = 1234
RETURNING amount;
Given that I know the previous amount I can simply diff them. But since this operation is not atomic, it is prone to race conditions.
Since RETURNING as far as I can tell can only return actual columns, I can't simply use that to return the diff.
So far the only solution I've come up with is to add an otherwise useless column maybe named previous_amount to payments and do something like this:
UPDATE payments SET
previous_amount = amount,
amount = LEAST(max_amount, amount + 50)
WHERE id = 1234
RETURNING previous_amount, amount;
But that seems kind of dumb. Is there a better way of doing this?
I'm doing this as part of an application, so the queries are executed from a Ruby/Sequel app.