ОБНОВЛЕНИЕ СТОЛ С СУММОЙ - PullRequest
0 голосов
/ 15 ноября 2010

У меня есть таблица с именем 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;

В этой таблице хранятся данные об управлении мелкой наличностью, но у меня есть простая проблема обновления баланса на определенную дату.Каждый раз, когда я вставляю, я запускаю следующий запрос:

UPDATE pettycash a SET pc_bal=SUM(pc_in-pc_out) WHERE pc_id=" & newID 

, но проблема возникает, когда кто-то приходит к публикации транзакций на предыдущую дату, например, вчера.Приведенный выше запрос обновит только одну строку, а остальные строки с более текущей датой будут иметь неправильные значения баланса.Есть ли запрос или хранимая процедура, которая обновит всю таблицу, получая правильный баланс для каждой даты?

1 Ответ

2 голосов
/ 15 ноября 2010

Триггеры , вероятно, вы хотите, чтобы вы хотели. Однако заставить его работать правильно и эффективно будет некрасиво. Вероятно, лучше не хранить баланс в каждой строке, если вы собираетесь вставлять строки в более ранние даты все так часто; вместо этого используйте запросы или просмотров , чтобы найти баланс. Чтобы найти баланс на определенную дату, объедините его со строками для более ранних дат и суммируйте чистый депозит, сгруппировав по текущему идентификатору транзакции:

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
;

Я также ограничиваю older.pc_id меньше, чем current.pc_id, чтобы исправить неоднозначность, касающуюся схемы и расчета баланса. Так как pc_date не уникален, вы можете иметь несколько транзакций на определенную дату. Если это так, каким должен быть баланс для каждой транзакции? Здесь мы предполагаем, что транзакция с большим идентификатором происходит после транзакции с меньшим идентификатором, но с той же датой. Более формально мы используем порядок

a> b & hArr; a.pc_date> b.pc_date & or; (a.pc_date = b.pc_date & and; a.pc_id> b.pc_id)

Обратите внимание, что в представлении мы используем & ge; заказ на основе>:

a & ge; b & hArr; a.pc_date> b.pc_date & or; (a.pc_date = b.pc_date & и; a.pc_id & ge; b.pc_id)

После попытки заставить триггеры работать должным образом, я рекомендую даже не пытаться. Из-за внутренней блокировки таблицы или строки при вставке / обновлении необходимо переместить столбец баланса в новую таблицу, хотя это не слишком обременительно (переименуйте pettycash в pettytransactions, создайте новую таблицу pettybalance (balance, pc_id) и создайте представление с именем pettycash, затем объедините pettytransactions и pettybalance в pc_id). Основная проблема заключается в том, что триггерные тела выполняются один раз для каждой созданной или обновленной строки, что делает их невероятно неэффективными. Альтернативой может быть создание хранимой процедуры для обновления столбцов, которую можно вызывать после вставки или обновления. Процедура является более производительной при получении балансов, чем представление, но более хрупкой, поскольку программисты должны обновлять балансы, а не позволять базе данных обрабатывать ее. Использование представления - более чистый дизайн.

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-тема

Проблема с текущей схемой заключается в использовании Float s для хранения денежных значений. Из-за того, как представлены числа с плавающей запятой, числа, которые являются точными в базе 10 (то есть не имеют повторяющегося десятичного представления), не всегда точны как числа с плавающей запятой. Например, 0,01 (в базе 10) будет ближе к 0,009999999776482582 ... или 0,0100000000000000002081668 ... при хранении. Это скорее похоже на то, как 1/3 в базе 3 равен «0,1», а 0,333333 ... в базе 10. Вместо Float, вы должны использовать тип Decimal:

ALTER TABLE pettycash MODIFY pc_in DECIMAL(10,2);
ALTER TABLE pettycash MODIFY pc_out DECIMAL(10,2);

Если вы используете вид, отпустите pettycash.pc_bal. Если для обновления pettycash.pc_bal используется хранимая процедура, ее тоже следует изменить.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...