Triggers är förmodligen vill du vill. Men att få detta att fungera korrekt och effektivt kommer att vara fult. Det är förmodligen bättre att inte lagra saldot i varje rad om du ska infoga rader vid tidigare datum så ofta; använd istället frågor eller vyer att hitta balansen. För att hitta saldot på ett visst datum, slå ihop det med raderna för tidigare datum och summera nettoinsättningen, grupperad efter aktuellt transaktions-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
;
Jag begränsar också older.pc_id
vara mindre än current.pc_id
för att fastställa en oklarhet som rör schemat och saldoberäkningen. Sedan pc_date
är inte unikt, du kan ha flera transaktioner för ett visst datum. Om så är fallet, vad ska saldot vara för varje transaktion? Här antar vi att en transaktion med ett större ID kommer efter en transaktion med ett mindre ID men som har samma datum. Mer formellt använder vi beställningen
Observera att i vyn använder vi en ≥ ordning baserad på>:
Efter att ha försökt få triggers att fungera korrekt, kommer jag att rekommendera att du inte ens försöker. På grund av interna tabell- eller radlås när du infogar/uppdateringar måste du flytta saldokolumnen till en ny tabell, även om detta inte är alltför betungande (byt namn på pettycash
till pettytransactions
, skapa en ny pettybalance (balance, pc_id)
tabell och skapa en vy som heter pettycash
than ansluter till pettytransactions
och pettybalance
på pc_id
). Huvudproblemet är att triggerkroppar körs en gång för varje rad som skapas eller uppdateras, vilket gör att de blir otroligt ineffektiva. Ett alternativ skulle vara att skapa en lagrad procedur
för att uppdatera kolumner, som du kan anropa efter att du har infogat eller uppdaterat. En procedur är mer presterande när man skaffar saldon än en vy, men mer skör eftersom det är upp till programmerare att uppdatera saldon, snarare än att låta databasen hantera det. Att använda en vy är den renare designen.
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
Ett problem med det aktuella schemat är användningen av Float
s för att lagra monetära värden. På grund av hur flyttalsnummer representeras, är tal som är exakta i bas 10 (dvs. inte har en upprepad decimalrepresentation) inte alltid exakta som flytande. Till exempel kommer 0,01 (i bas 10) att vara närmare 0,009999999776482582... eller 0,010000000000000000002081668... när den lagras. Det är ungefär som hur 1/3 i bas 3 är "0,1" men 0,333333.... i bas 10. Istället för Float
, bör du använda Decimal
typ:
ALTER TABLE pettycash MODIFY pc_in DECIMAL(10,2);
ALTER TABLE pettycash MODIFY pc_out DECIMAL(10,2);
Om du använder en vy, släpp pettycash.pc_bal
. Om du använder en lagrad procedur för att uppdatera pettycash.pc_bal
, bör den också ändras.