Triggers er sandsynligvis ønsker du ønsker. Det vil dog være grimt at få dette til at fungere korrekt og effektivt. Det er nok bedre ikke at gemme saldoen i hver række, hvis du så ofte skal indsætte rækker på tidligere datoer; brug i stedet forespørgsler eller visninger at finde balancen. For at finde saldoen på en bestemt dato skal du slå den sammen med rækkerne for tidligere datoer og summere nettoindbetalingen, grupperet efter det aktuelle 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
;
Jeg begrænser også older.pc_id
at være mindre end current.pc_id
for at fastlægge en uklarhed vedrørende skemaet og saldoberegningen. Siden pc_date
er ikke unik, kan du have flere transaktioner for en given dato. Hvis det er tilfældet, hvad skal saldoen så være for hver transaktion? Her antager vi, at en transaktion med et større ID kommer efter en transaktion med et mindre ID, men som har samme dato. Mere formelt bruger vi bestillingen
Bemærk, at vi i visningen bruger en ≥ rækkefølge baseret på>:
Efter at have forsøgt at få triggere til at fungere korrekt, vil jeg anbefale ikke engang at prøve. På grund af interne tabel- eller rækkelåse ved indsættelse/opdatering, skal du flytte saldokolonnen til en ny tabel, selvom dette ikke er for besværligt (omdøb pettycash
til pettytransactions
, opret en ny pettybalance (balance, pc_id)
tabel, og opret en visning med navnet pettycash
end slutter sig til pettytransactions
og pettybalance
på pc_id
). Hovedproblemet er, at trigger-kroppe udføres én gang for hver række, der er oprettet eller opdateret, hvilket vil få dem til at være utroligt ineffektive. Et alternativ ville være at oprette en lagret procedure
at opdatere kolonner, som du kan kalde efter indsættelse eller opdatering. En procedure er mere effektiv, når man får saldi end en visning, men mere skør, da det er op til programmører at opdatere saldi i stedet for at lade databasen håndtere det. At bruge en visning er det renere design.
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 ;
Udenfor emnet
Et problem med det aktuelle skema er brugen af Float
s at gemme pengeværdier. På grund af hvordan flydende kommatal repræsenteres, er tal, der er nøjagtige i grundtallet 10 (dvs. ikke har en gentagende decimalrepræsentation) ikke altid nøjagtige som flydere. For eksempel vil 0,01 (i basis 10) være tættere på 0,009999999776482582... eller 0,010000000000000000002081668... når den er gemt. Det er lidt ligesom, hvordan 1/3 i base 3 er "0,1", men 0,333333.... i base 10. I stedet for Float
, skal du bruge Decimal
type:
ALTER TABLE pettycash MODIFY pc_in DECIMAL(10,2);
ALTER TABLE pettycash MODIFY pc_out DECIMAL(10,2);
Hvis du bruger en visning, skal du slippe pettycash.pc_bal
. Hvis du bruger en lagret procedure til at opdatere pettycash.pc_bal
, bør den også ændres.