Возврат изменения в обновленном значении в PostgreSQL - PullRequest
0 голосов
/ 05 февраля 2019

Предположим, у меня есть две таблицы: 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 может возвращать только фактические столбцы, я не могу просто использовать это для возврата diff.

Пока что единственное решение, которое я нашел, - это добавить бесполезный столбец с именем 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 голосов
/ 05 февраля 2019

Это хорошее приложение для триггера:

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;
...