Acionadores são provavelmente o que você quer. No entanto, fazer isso funcionar de forma adequada e eficiente será feio. Provavelmente é melhor não armazenar o saldo em cada linha se você for inserir linhas em datas anteriores com tanta frequência; em vez disso, use consultas ou visualizações para encontrar o equilíbrio. Para encontrar o saldo em uma determinada data, una-o com as linhas de datas anteriores e some o depósito líquido, agrupando pelo ID da transação atual:
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
;
Eu também restrinjo
older.pc_id
seja menor que current.pc_id
para corrigir uma ambiguidade relativa ao esquema e ao cálculo do saldo. Desde o pc_date
não é único, você pode ter várias transações para uma determinada data. Se for esse o caso, qual deve ser o saldo para cada transação? Aqui assumimos que uma transação com um ID maior vem depois de uma transação com um ID menor, mas que tem a mesma data. Mais formalmente, usamos a ordenação Observe que na visualização, usamos uma ordem ≥ baseada em>:
Depois de tentar fazer com que os gatilhos funcionem corretamente, vou recomendar nem tentar. Devido a bloqueios internos de tabelas ou linhas ao inserir/atualizar, você precisa mover a coluna de saldo para uma nova tabela, embora isso não seja muito oneroso (renomear
pettycash
para pettytransactions
, crie um novo pettybalance (balance, pc_id)
table e crie uma visão chamada pettycash
do que une pettytransactions
e pettybalance
em pc_id
). O principal problema é que os corpos dos gatilhos são executados uma vez para cada linha criada ou atualizada, o que os tornará incrivelmente ineficientes. Uma alternativa seria criar um procedimento armazenado
para atualizar colunas, que você pode chamar após inserir ou atualizar. Um procedimento é mais eficiente ao obter saldos do que uma exibição, mas mais frágil, pois cabe aos programadores atualizar os saldos, em vez de deixar o banco de dados lidar com isso. Usar uma visualização é o design mais limpo. 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 ;
Fora do tópico
Um problema com o esquema atual é o uso de
Float
s para armazenar valores monetários. Devido à forma como os números de ponto flutuante são representados, os números que são exatos na base 10 (ou seja, não têm uma representação decimal periódica) nem sempre são exatos como floats. Por exemplo, 0,01 (na base 10) estará mais próximo de 0,009999999776482582... ou 0,0100000000000000002081668... quando armazenado. É mais ou menos como 1/3 na base 3 é "0,1" mas 0,333333.... na base 10. Em vez de Float
, você deve usar o Decimal
modelo:ALTER TABLE pettycash MODIFY pc_in DECIMAL(10,2);
ALTER TABLE pettycash MODIFY pc_out DECIMAL(10,2);
Se estiver usando uma visualização, solte
pettycash.pc_bal
. Se estiver usando um procedimento armazenado para atualizar pettycash.pc_bal
, ele também deve ser alterado.