Trigger probabilmente vuoi tu vuoi. Tuttavia, farlo funzionare correttamente ed efficientemente sarà brutto. Probabilmente è meglio non memorizzare il saldo in ogni riga se si inseriscono righe in date precedenti molto frequentemente; invece, usa query o viste per trovare l'equilibrio. Per trovare il saldo in una data particolare, uniscilo alle righe delle date precedenti e somma il deposito netto, raggruppandolo per ID transazione corrente:
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
;
Limito anche older.pc_id
essere inferiore a current.pc_id
al fine di correggere un'ambiguità relativa allo schema e al calcolo del saldo. Dal pc_date
non è univoco, potresti avere più transazioni per una determinata data. In tal caso, quale dovrebbe essere il saldo per ciascuna transazione? Qui assumiamo che una transazione con un ID più grande avvenga dopo una transazione con un ID più piccolo ma che ha la stessa data. Più formalmente, utilizziamo l'ordinamento
Nota che nella vista, utilizziamo un ordine ≥ basato su>:
Dopo aver provato a far funzionare correttamente i trigger, ti consiglierò di non provare nemmeno. A causa di blocchi di righe o tabelle interne durante l'inserimento/l'aggiornamento, è necessario spostare la colonna del saldo in una nuova tabella, anche se questo non è troppo oneroso (rinominare pettycash
a pettytransactions
, crea un nuovo pettybalance (balance, pc_id)
tabella e crea una vista denominata pettycash
che si unisce a pettytransactions
e pettybalance
su pc_id
). Il problema principale è che i corpi trigger vengono eseguiti una volta per ogni riga creata o aggiornata, il che li renderà incredibilmente inefficienti. Un'alternativa sarebbe creare una procedura memorizzata
per aggiornare le colonne, che puoi chiamare dopo l'inserimento o l'aggiornamento. Una procedura è più performante quando si ottengono saldi rispetto a una vista, ma più fragile poiché spetta ai programmatori aggiornare i saldi, piuttosto che lasciare che sia il database a gestirlo. L'uso di una vista è il design più pulito.
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 ;
Fuori tema
Un problema con lo schema corrente è l'uso di Float
s per memorizzare valori monetari. A causa del modo in cui vengono rappresentati i numeri in virgola mobile, i numeri esatti in base 10 (cioè non hanno una rappresentazione decimale ripetuta) non sono sempre esatti come float. Ad esempio, 0,01 (in base 10) sarà più vicino a 0,0099999999776482582... o 0,01000000000000000002081668... quando memorizzato. È un po' come 1/3 in base 3 è "0.1" ma 0.333333.... in base 10. Invece di Float
, dovresti usare Decimal
digita:
ALTER TABLE pettycash MODIFY pc_in DECIMAL(10,2);
ALTER TABLE pettycash MODIFY pc_out DECIMAL(10,2);
Se utilizzi una vista, rilascia pettycash.pc_bal
. Se si utilizza una procedura memorizzata per aggiornare pettycash.pc_bal
, anch'esso dovrebbe essere modificato.