Wyzwalacze są prawdopodobnie chcesz chcesz. Jednak sprawienie, aby to działało poprawnie i wydajnie, będzie brzydkie. Prawdopodobnie lepiej nie przechowywać salda w każdym wierszu, jeśli zamierzasz wstawiać wiersze we wcześniejszych terminach tak często; zamiast tego użyj zapytań lub widoków znaleźć równowagę. Aby znaleźć saldo w określonym dniu, połącz je z wierszami dla wcześniejszych dat i zsumuj depozyt netto, grupując według bieżącego identyfikatora transakcji:
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
;
Ograniczam również older.pc_id
być mniejsze niż current.pc_id
w celu ustalenia niejasności dotyczących schematu i kalkulacji salda. Od pc_date
nie jest unikalny, możesz mieć wiele transakcji w danym dniu. Jeśli tak jest, jakie powinno być saldo dla każdej transakcji? Tutaj zakładamy, że transakcja z większym identyfikatorem następuje po transakcji z mniejszym identyfikatorem, ale ma tę samą datę. Bardziej formalnie korzystamy z zamówienia
Zauważ, że w widoku używamy kolejności ≥ opartej na>:
Po próbie sprawienia, aby wyzwalacze działały prawidłowo, radzę nawet nie próbować. Ze względu na wewnętrzne blokady tabeli lub wiersza podczas wstawiania/aktualizowania, musisz przenieść kolumnę salda do nowej tabeli, choć nie jest to zbyt uciążliwe (zmień nazwę pettycash
do pettytransactions
, utwórz nowy pettybalance (balance, pc_id)
tabeli i utwórz widok o nazwie pettycash
niż dołącza do pettytransactions
i pettybalance
na pc_id
). Główny problem polega na tym, że ciała wyzwalające są wykonywane raz dla każdego utworzonego lub zaktualizowanego wiersza, co spowoduje, że będą one niewiarygodnie nieefektywne. Alternatywą byłoby utworzenie procedury składowanej
do aktualizacji kolumn, które możesz wywołać po wstawieniu lub aktualizacji. Procedura jest bardziej wydajna podczas pobierania sald niż widok, ale jest bardziej krucha, ponieważ to programiści powinni aktualizować salda, zamiast pozwolić, aby obsłużyła je baza danych. Korzystanie z widoku to czystszy projekt.
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 ;
Nie na temat
Problemem z obecnym schematem jest użycie Float
s do przechowywania wartości pieniężnych. Ze względu na sposób przedstawiania liczb zmiennoprzecinkowych, liczby o podstawie 10 (tzn. nie mają powtarzalnej reprezentacji dziesiętnej) nie zawsze są dokładne jako liczby zmiennoprzecinkowe. Na przykład 0,01 (w podstawie 10) będzie bliższe 0,009999999776482582... lub 0,0100000000000000002081668... podczas przechowywania. To raczej tak, jak 1/3 w bazie 3 to "0.1", ale 0.333333.... w bazie 10. Zamiast Float
, należy użyć Decimal
typ:
ALTER TABLE pettycash MODIFY pc_in DECIMAL(10,2);
ALTER TABLE pettycash MODIFY pc_out DECIMAL(10,2);
Jeśli używasz widoku, upuść pettycash.pc_bal
. Jeśli używasz procedury składowanej do aktualizacji pettycash.pc_bal
, to też powinno zostać zmienione.