I have a table called pettycash
CREATE TABLE `pettycash` (
`pc_id` int(7) NOT NULL AUTO开发者_运维技巧_INCREMENT,
`pc_date` date NOT NULL,
`pc_in` double(13,2) DEFAULT '0.00',
`pc_out` double(13,2) DEFAULT '0.00',
`pc_bal` double(13,2) DEFAULT '0.00',
`pc_ref` varchar(95) DEFAULT NULL,
`pc_user` varchar(65) DEFAULT NULL,
`pc_terminal` varchar(128) DEFAULT NULL,
`pc_void` tinyint(1) DEFAULT '0',
PRIMARY KEY (`pc_id`)
) ENGINE=MyISAM AUTO_INCREMENT=3 DEFAULT CHARSET=latin1;
This table stores data about the petty cash management,but i have a simple problem of updating the balance as at a particular date. Each time i insert i run the following query:
UPDATE pettycash a SET pc_bal=SUM(pc_in-pc_out) WHERE pc_id=" & newID
but the problem comes when someone comes to post transactions for a previous date like yesterday. the above query will only update one row and the other rows of a more current date will have wrong balance values. Is there a query or a Stored Procedure that will update the whole table getting the correct balance for each date?
Triggers are probably want you want. However, getting this to work properly and efficiently will be ugly. It's probably better not to store the balance in each row if you're going to be inserting rows at earlier dates all that frequently; instead, use queries or views to find the balance. To find the balance on a particular date, join it with the rows for earlier dates and sum the net deposit, grouping by the current transaction 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
;
I also restrict older.pc_id
to be less than current.pc_id
in order to fix an ambiguity relating to the schema and the balance calculation. Since the pc_date
isn't unique, you could have multiple transactions for a given date. If that's the case, what should the balance be for each transaction? Here we assume that a transaction with a larger ID comes after a transaction with a smaller ID but that has the same date. More formally, we use the ordering
a > b ⇔ a.pc_date > b.pc_date ∨ (a.pc_date = b.pc_date ∧ a.pc_id > b.pc_id)
Note that in the view, we use a ≥ order based on >:
a ≥ b ⇔ a.pc_date > b.pc_date ∨ (a.pc_date = b.pc_date ∧ a.pc_id ≥ b.pc_id)
After trying to get triggers to work properly, I'm going to recommend not even trying. Due to internal table or row locks when inserting/updating, you have to move the balance column to a new table, though this isn't too onerous (rename pettycash
to pettytransactions
, create a new pettybalance (balance, pc_id)
table, and create a view named pettycash
than joins pettytransactions
and pettybalance
on pc_id
). The main problem is that trigger bodies execute once for each row created or updated, which will cause them to be incredibly inefficient. An alternative would be to create a stored procedure to update columns, which you can call after inserting or updating. A procedure is more performant when getting balances than a view, but more brittle as it's up to programmers to update balances, rather than letting the database handle it. Using a view is the cleaner 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 ;
Off-topic
A problem with the current schema is the use of Float
s to store monetary values. Due to how floating point numbers are represented, numbers that are exact in base 10 (i.e. don't have a repeating decimal representation) aren't always exact as floats. For example, 0.01 (in base 10) will be closer to 0.009999999776482582... or 0.0100000000000000002081668... when stored. It's rather like how 1/3 in base 3 is "0.1" but 0.333333.... in base 10. Instead of Float
, you should use the Decimal
type:
ALTER TABLE pettycash MODIFY pc_in DECIMAL(10,2);
ALTER TABLE pettycash MODIFY pc_out DECIMAL(10,2);
If using a view, drop pettycash.pc_bal
. If using a stored procedure to update pettycash.pc_bal
, it too should be altered.
精彩评论