Задействания вероятно искате искате. Въпреки това ще бъде грозно да накарате това да работи правилно и ефективно. Вероятно е по-добре да не съхранявате баланса във всеки ред, ако ще вмъквате редове на по-ранни дати толкова често; вместо това използвайте заявки или изгледи да намерим баланса. За да намерите салдото на определена дата, съединете го с редовете за по-ранни дати и сумирайте нетния депозит, групирайки по текущия идентификатор на транзакция:
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
не е уникален, можете да имате множество транзакции за дадена дата. Ако случаят е такъв, какво трябва да бъде салдото за всяка транзакция? Тук приемаме, че транзакция с по-голям идентификатор идва след транзакция с по-малък идентификатор, но която има същата дата. По-формално, ние използваме подреждането
Обърнете внимание, че в изгледа използваме ≥ ред въз основа на>:
След като се опитате да накарате тригерите да работят правилно, ще препоръчам дори да не опитвате. Поради вътрешни блокировки на таблица или ред при вмъкване/актуализиране, трябва да преместите колоната за баланса в нова таблица, въпреки че това не е твърде обременително (преименувайте 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 ;
Извън темата
Проблем с текущата схема е използването на 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
, то също трябва да бъде променено.