Triggers zijn waarschijnlijk wilt u wilt. Het zal echter lelijk zijn om dit goed en efficiënt te laten werken. Het is waarschijnlijk beter om het saldo niet in elke rij op te slaan als u zo vaak rijen op eerdere data gaat invoegen; gebruik in plaats daarvan zoekopdrachten of weergaven om de balans te vinden. Om het saldo op een bepaalde datum te vinden, voegt u het samen met de rijen voor eerdere datums en telt u de netto-storting op, gegroepeerd op de huidige transactie-ID:
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
;
Ik beperk ook older.pc_id
kleiner zijn dan current.pc_id
om een dubbelzinnigheid met betrekking tot het schema en de balansberekening op te lossen. Sinds de pc_date
niet uniek is, kunt u meerdere transacties hebben voor een bepaalde datum. Als dat het geval is, wat moet dan het saldo zijn voor elke transactie? Hierbij gaan we ervan uit dat een transactie met een grotere ID komt na een transactie met een kleinere ID maar die dezelfde datum heeft. Meer formeel gebruiken we de volgorde
Merk op dat we in de weergave een ≥-volgorde gebruiken op basis van>:
Nadat ik geprobeerd heb om triggers goed te laten werken, raad ik aan om het niet eens te proberen. Vanwege interne tabel- of rijvergrendelingen bij het invoegen/bijwerken, moet u de saldokolom naar een nieuwe tabel verplaatsen, hoewel dit niet al te omslachtig is (hernoem pettycash
naar pettytransactions
, maak een nieuwe pettybalance (balance, pc_id)
tabel, en maak een weergave met de naam pettycash
dan sluit zich aan bij pettytransactions
en pettybalance
op pc_id
). Het grootste probleem is dat trigger-instanties één keer worden uitgevoerd voor elke gemaakte of bijgewerkte rij, waardoor ze ongelooflijk inefficiënt zijn. Een alternatief zou zijn om een opgeslagen procedure
te maken om kolommen bij te werken, die u kunt oproepen na het invoegen of bijwerken. Een procedure is performanter bij het verkrijgen van saldi dan een weergave, maar brozer omdat het aan programmeurs is om saldi bij te werken, in plaats van de database het te laten afhandelen. Het gebruik van een weergave is het schonere ontwerp.
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-topic
Een probleem met het huidige schema is het gebruik van Float
s om geldwaarden op te slaan. Vanwege de manier waarop getallen met drijvende komma worden weergegeven, zijn getallen die exact zijn in grondtal 10 (d.w.z. geen herhalende decimale representatie hebben) niet altijd exact als drijvers. Bijvoorbeeld, 0,01 (in grondtal 10) zal dichter bij 0,009999999776482582... of 0,0100000000000002081668... liggen wanneer deze wordt opgeslagen. Het is een beetje zoals hoe 1/3 in basis 3 "0.1" is, maar 0.333333.... in basis 10. In plaats van Float
, moet u de Decimal
typ:
ALTER TABLE pettycash MODIFY pc_in DECIMAL(10,2);
ALTER TABLE pettycash MODIFY pc_out DECIMAL(10,2);
Als u een weergave gebruikt, laat u pettycash.pc_bal
. vallen . Als u een opgeslagen procedure gebruikt om pettycash.pc_bal
bij te werken, , moet ook worden gewijzigd.