Триггеры , вероятно, вы хотите, чтобы вы хотели. Однако заставить его работать правильно и эффективно будет некрасиво. Вероятно, лучше не хранить баланс в каждой строке, если вы собираетесь вставлять строки в более ранние даты все так часто; вместо этого используйте запросы или просмотров , чтобы найти баланс. Чтобы найти баланс на определенную дату, объедините его со строками для более ранних дат и суммируйте чистый депозит, сгруппировав по текущему идентификатору транзакции:
CREATE VIEW pettybalance
AS SELECT SUM(older.pc_in - older.pc_out) AS balance,
current.pc_id AS pc_id, -- foreign key
current.pc_date AS `date`
FROM pettycash AS current
JOIN pettycash AS older
ON current.pc_date > older.pc_date
OR (current.pc_date = older.pc_date AND current.pc_id >= older.pc_id)
GROUP BY current.pc_id
;
Я также ограничиваю older.pc_id
меньше, чем current.pc_id
, чтобы исправить неоднозначность, касающуюся схемы и расчета баланса. Так как pc_date
не уникален, вы можете иметь несколько транзакций на определенную дату. Если это так, каким должен быть баланс для каждой транзакции? Здесь мы предполагаем, что транзакция с большим идентификатором происходит после транзакции с меньшим идентификатором, но с той же датой. Более формально мы используем порядок
a> b & hArr; a.pc_date> b.pc_date & or; (a.pc_date = b.pc_date & and; a.pc_id> b.pc_id)
Обратите внимание, что в представлении мы используем & ge; заказ на основе>:
a & ge; b & hArr; a.pc_date> b.pc_date & or; (a.pc_date = b.pc_date & и; a.pc_id & ge; b.pc_id)
После попытки заставить триггеры работать должным образом, я рекомендую даже не пытаться. Из-за внутренней блокировки таблицы или строки при вставке / обновлении необходимо переместить столбец баланса в новую таблицу, хотя это не слишком обременительно (переименуйте pettycash
в pettytransactions
, создайте новую таблицу pettybalance (balance, pc_id)
и создайте представление с именем pettycash
, затем объедините pettytransactions
и pettybalance
в pc_id
). Основная проблема заключается в том, что триггерные тела выполняются один раз для каждой созданной или обновленной строки, что делает их невероятно неэффективными. Альтернативой может быть создание хранимой процедуры для обновления столбцов, которую можно вызывать после вставки или обновления. Процедура является более производительной при получении балансов, чем представление, но более хрупкой, поскольку программисты должны обновлять балансы, а не позволять базе данных обрабатывать ее. Использование представления - более чистый дизайн.
DROP PROCEDURE IF EXISTS update_balance;
delimiter ;;
CREATE PROCEDURE update_balance (since DATETIME)
BEGIN
DECLARE sincebal DECIMAL(10,2);
SET sincebal = (
SELECT pc_bal
FROM pettycash AS pc
WHERE pc.pc_date < since
ORDER BY pc.pc_date DESC, pc.pc_id DESC LIMIT 1
);
IF ISNULL(sincebal) THEN
SET sincebal=0.0;
END IF;
UPDATE pettycash AS pc
SET pc_bal=(
SELECT sincebal+SUM(net)
FROM (
SELECT pc_id, pc_in - pc_out AS net, pc_date
FROM pettycash
WHERE since <= pc_date
) AS older
WHERE pc.pc_date > older.pc_date
OR (pc.pc_date = older.pc_date
AND pc.pc_id >= older.pc_id)
) WHERE pc.pc_date >= since;
END;;
delimiter ;
Off-тема
Проблема с текущей схемой заключается в использовании Float
s для хранения денежных значений. Из-за того, как представлены числа с плавающей запятой, числа, которые являются точными в базе 10 (то есть не имеют повторяющегося десятичного представления), не всегда точны как числа с плавающей запятой. Например, 0,01 (в базе 10) будет ближе к 0,009999999776482582 ... или 0,0100000000000000002081668 ... при хранении. Это скорее похоже на то, как 1/3 в базе 3 равен «0,1», а 0,333333 ... в базе 10. Вместо Float
, вы должны использовать тип Decimal
:
ALTER TABLE pettycash MODIFY pc_in DECIMAL(10,2);
ALTER TABLE pettycash MODIFY pc_out DECIMAL(10,2);
Если вы используете вид, отпустите pettycash.pc_bal
. Если для обновления pettycash.pc_bal
используется хранимая процедура, ее тоже следует изменить.