开发者

UPDATE TABLE WITH SUM

开发者 https://www.devze.com 2023-01-24 21:47 出处:网络
I have a table called pettycash CREATE TABLE `pettycash` ( `pc_id` int(7) NOT NULL AUTO开发者_运维技巧_INCREMENT,

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 Floats 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.

0

精彩评论

暂无评论...
验证码 换一张
取 消