根据交易数据计算当前成员余额

时间:2010-11-20 05:53:37

标签: sql mysql

我有一张MySQL金融交易表。

表格如下:

+----+-----------+--------+--------+---------+---------+
| id | member_id | status | amount | opening | closing |
+----+-----------+--------+--------+---------+---------+
| 1  | 2         | 1      | 1000   | 0       | 1000    |
| 2  | 2         | 2      | 100    | 1000    | 1000    |
| 3  | 2         | 1      | -20    | 1000    | 980     |
| 4  | 2         | 1      | 10     | 980     | 990     |
+----+-----------+--------+--------+---------+---------+

目前,开始和结束字段为空。状态1已提交,2未提交。

有人可以告诉我如何编写查询来扫描整个表并更新所有期初和期末余额吗?

此表中有近1,000,000条记录,因此如果查询得到了很好的优化,那就太好了。此外,实时关键任务数据也不需要这样做。它只是当前对成员平衡的估计。

7 个答案:

答案 0 :(得分:3)

ajreal的解决方案存在疏忽 - 它不会为不同的成员ID重置平衡变量。这个版本解决了这个问题。

这有效(我测试过):

set @clo:=0, @opn:=0, @mem:=0; 
update member_txns 
set
  opening=if(status=1, @opn:=if(@mem=(@mem:=member_id), @clo, 0), @clo), 
  closing=if(status=1, @clo:=@opn+amount, @clo) 
order by member_id, id;

答案 1 :(得分:2)

你真的不想以这种方式实现它。当你需要回复交易日期时,你不会发现自己会受到伤害的世界。 1m记录不是那么多,所以索引应该是足够的......

答案 2 :(得分:0)

  1. 存储余额是非正规化,不需要
  2. 如果您确实要存储余额,则应该在“成员”表中
  3. 一个简单的SELECT member_id, SUM(amount) as Balance GROUP BY member_id会给你结果。如果您确实遇到了性能问题,可以随时将nice trick by Allen Browne转换为您的情况。

答案 3 :(得分:0)

这是尝试分析情况

首先,我使用小python脚本创建了1M记录

import csv
import random
ofile = open('sample.csv', "wb")
writer = csv.writer(ofile)
for i in xrange(1000000):
    row = [ i, i/(5+random.randint(0,10)), 1+random.randint(0,10)/10, random.randint(10, 200)*10, 0 ]   
    writer.writerow(row)
ofile.close()

然后创建表事务

CREATE TABLE `transactions` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `member_id` int(10) unsigned NOT NULL,
  `status` tinyint(3) unsigned NOT NULL,
  `amount` decimal(10,2) NOT NULL,
  `opening` decimal(10,2) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=MyISAM;

(从中我放弃了期末余额,那是一个没有意义的非规范化)

然后使用

加载数据
LOAD DATA LOCAL INFILE 'sample.csv' INTO TABLE transactions FIELDS TERMINATED BY ',';

然而,我的第一个问题是建立不良性能,以下查询,没有添加任何索引在0.10秒内运行

SELECT SQL_NO_CACHE member_id, SUM(amount) 
FROM transactions 
WHERE member_id between 500 and 1000 AND status = 1 
GROUP BY member_id;

然后我尝试加入成员表,我从现有的交易数据创建了成员表,但查询

SELECT SQL_NO_CACHE member_id, name, SUM(amount) 
FROM transactions JOIN members ON transactions.member_id = members.id 
WHERE member_id between 500 and 1000 AND status = 1 
GROUP BY member_id;

以0.17秒的速度运行

所以,我无法复制问题。因此,我建议将上述查询作为解决方案,或者请求更多信息以尝试复制问题。

答案 4 :(得分:0)

解决方案需要一个程序 - 我将通过存储过程显示它。

SQL在依赖于基于值未知的列(例如id列)的表中的行顺序的查询时尤其糟糕。解决此类案件的唯一方法是使用某种可执行代码来扫描数据。

虽然你没有说明,但我认为逻辑是:

  1. 处理按成员和ID排序的行
  2. 忽略status = 2
  3. 的行
  4. 如果是成员
  5. 的第一行,则将开场设置为0
  6. 将结算更新为开头加上金额
  7. 请注意,这里有一个明显的问题:如果状态发生变化(从2到1或1到2)会发生什么?这需要从

    的那一点重新计算该成员的交易

    这是执行此操作的存储过程:

    drop procedure if exists calculate_balances;
    delimiter ~
    create procedure calculate_balances()
    comment 'calculates running balances'
    begin
    declare _id int;
    declare _member_id int;
    declare _amount int;
    declare _balance int;
    declare _current_member_id int default 0;
    declare _done int default 0;
    
    declare _cursor cursor for
    select id, member_id, case when status = 1 then amount else 0 end
    from member_txns
    order by member_id, id;
    
    declare continue handler for not found set _done = 1;
    
    open _cursor;
    
    repeat
      fetch _cursor into _id, _member_id, _amount;
    
      if not _done then
        if _current_member_id != _member_id then
          set _balance = 0;
          set _current_member_id = _member_id;
        end if;
    
        update member_txns set opening = _balance, closing = _balance + _amount where id = _id;
    
        set _balance = _balance + _amount;
      end if;
    until _done end repeat;
    
    end;~
    delimiter ;
    

    请注意,状态处理在查询中处理。

    以下是要测试的代码:

    create table member_txns ( 
    id int,
    member_id int,
    status int,
    amount int,
    opening int,
    closing int
    );
    
    insert into member_txns (id, member_id, status, amount) values 
    (1,2,1,1000),
    (2,2,2,100),
    (3,2,1,-20),
    (4,2,1,10),
    (5,3,1,-20),
    (6,3,1,100);
    
    call calculate_balances();
    
    select * from member_txns;
    

    执行这些命令将显示存储过程产生正确的结果。

答案 5 :(得分:0)

为什么不从存储过程创建物化视图?它将数据和计算值之间的逻辑分开。它还可以提供最快的查询。

答案 6 :(得分:0)

您没有说明状态从2变为1时会发生什么,它后面的所有数据是否都需要相应地更新开始和结束值?如果没有那么你的系统有严重的问题。如果是这样,那么你需要多次重新计算所有这些数据,这就是你首先提出这个问题的原因吗?

首先,没有理由为所有记录存储打开和关闭值, 结束=开启+(当状态= 1时,数量等于0结束),这将是您需要的计算的一半。

接下来,或许你最好只将每10,20,50或100+个记录存储为关键点。因此,您只对100个ID值的括号进行汇总,总计最多为73020.如果您真的想要,可以将其更改为也包括汇总级别的所有先前值的总运行总计。

通常,财务系统仅以月为单位存储期初和结算价值,并在该月内重新计算。考虑一下您的用户实际需要这些数据的频率与过度复杂性相比,当状态“2”变为状态“1”时必须不断更新它

我建议您存储周期性值,例如:

+--------+--------+---------+-------+
| Period | Member | Opening | Total |
+--------+--------+---------+-------+
| 1      | 1      | 0       | 50    |
| 2      | 1      | 50      | 1000  |
| 2      | 2      | 0       | 100   |
| 3      | 1      | 1050    | 0     |
| 3      | 2      | 50      | 600   |
+--------+--------+---------+-------+

依此类推,这是一个更好的数据模式,维护起来相当容易。

要更新这些值,您只需确定需要更新的时间段,然后重新计算该时间段的总数,然后只需更新所有后续的“打开”值。即使拥有30,000名不同的会员,您也需要4年才能获得这个“期间表”达到100万条记录(假设您使用月度期限),在这段时间内,我希望您的交易量大于10-50(10-50的订单)因此,实施此实施可将工作量减少10-50倍,从而对最终用户的性能产生极小的影响。