Activadores probablemente quieras que quieras. Sin embargo, hacer que esto funcione de manera adecuada y eficiente será feo. Probablemente sea mejor no almacenar el saldo en cada fila si va a insertar filas en fechas anteriores con tanta frecuencia; en su lugar, use consultas o vistas para encontrar el equilibrio. Para encontrar el saldo en una fecha en particular, únalo con las filas de fechas anteriores y sume el depósito neto, agrupándolo por el ID de transacción actual:
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
;
También restrinjo older.pc_id
ser menor que current.pc_id
para corregir una ambigüedad relacionada con el esquema y el cálculo del saldo. Desde el pc_date
no es único, podría tener varias transacciones para una fecha determinada. Si ese es el caso, ¿cuál debería ser el saldo de cada transacción? Aquí asumimos que una transacción con una identificación más grande viene después de una transacción con una identificación más pequeña pero que tiene la misma fecha. Más formalmente, usamos la ordenación
Tenga en cuenta que en la vista, usamos un orden ≥ basado en>:
Después de intentar que los disparadores funcionen correctamente, recomendaré que ni siquiera lo intentes. Debido a bloqueos internos de tablas o filas al insertar/actualizar, debe mover la columna de saldo a una nueva tabla, aunque esto no es demasiado oneroso (cambie el nombre de pettycash
a pettytransactions
, cree un nuevo pettybalance (balance, pc_id)
y crea una vista llamada pettycash
que se une a pettytransactions
y pettybalance
en pc_id
). El principal problema es que los cuerpos desencadenantes se ejecutan una vez por cada fila creada o actualizada, lo que hará que sean increíblemente ineficientes. Una alternativa sería crear un procedimiento almacenado
para actualizar columnas, a las que puede llamar después de insertar o actualizar. Un procedimiento es más eficaz al obtener saldos que una vista, pero es más frágil ya que depende de los programadores actualizar los saldos, en lugar de dejar que la base de datos los maneje. Usar una vista es el diseño más limpio.
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 ;
Fuera del tema
Un problema con el esquema actual es el uso de Float
s para almacenar valores monetarios. Debido a cómo se representan los números de coma flotante, los números que son exactos en base 10 (es decir, no tienen una representación decimal periódica) no siempre son exactos como flotantes. Por ejemplo, 0.01 (en base 10) estará más cerca de 0.009999999776482582... o 0.0100000000000000002081668... cuando se almacena. Es como si 1/3 en base 3 fuera "0,1" pero 0,333333... en base 10. En lugar de Float
, debe usar el Decimal
tipo:
ALTER TABLE pettycash MODIFY pc_in DECIMAL(10,2);
ALTER TABLE pettycash MODIFY pc_out DECIMAL(10,2);
Si usa una vista, suelte pettycash.pc_bal
. Si utiliza un procedimiento almacenado para actualizar pettycash.pc_bal
, también debería modificarse.