在 mysql 或更好的 algothrim 中将每笔交易的借方与贷方匹配?

Matching debit with credit per transaction in mysql or with better algothrim?

首先让我解释一下 table 结构和我需要的输出。

userid  date           amount 
123    2017-01-01         5 
123    2017-01-03         2 
124    2017-01-04         2 
124    2017-01-04         3 
123    2017-01-05         -2

借方交易为负数,正数交易为会员贷方交易。 我们可以通过此查询轻松制作帐户报表

select date as BookingDate,
       if(amount<0,amount,0) as Debit_Amount,
       if(amount>0,amount,0) as Credit_Amount,
       (@runtot :=  amount  + @runtot) AS Balance  
from transactions,
     (SELECT @runtot:=0) c 
where userid=123


BookingDate  Debit_Amount  Credit_Amount  Balance 
2017-01-01    0                 5           5 
2017-01-03    0                 2           7 
2017-01-05   -2                0           5

我的要求是使用 FIFO 方法根据借方交易标记每笔交易已支付或部分支付。像这样。这可以通过 mysql 查询或更好的算法实现吗?

 userid  date           amount   status
    123    2017-01-01         5   partial_paid(-2)
    123    2017-01-03         2 
    124    2017-01-04         2 
    124    2017-01-04         3 
    123    2017-01-05         -2

谢谢

MariaDB [sandbox]> create table t(userid  int,dt date,  amount int);
Query OK, 0 rows affected (0.28 sec)

MariaDB [sandbox]> truncate table t;
Query OK, 0 rows affected (0.20 sec)

MariaDB [sandbox]> insert into t values
    -> (123  ,  '2017-01-01'  ,       5) ,
    -> (123  ,  '2017-01-03'  ,       2 ),
    -> (124  ,  '2017-01-04'  ,       2) ,
    -> (124  ,  '2017-01-04'  ,       3 ),
    -> (123  ,  '2017-01-05'  ,       -2),
    -> (125  ,  '2017-01-01'  ,       5) ,
    -> (125  ,  '2017-01-03'  ,       2 ),
    -> (125  ,  '2017-01-05'  ,       -6),
    -> (126  ,  '2017-01-01'  ,       5) ,
    -> (126  ,  '2017-01-02'  ,       -10),
    -> (126  ,  '2017-01-03'  ,       2 ),
    -> (126  ,  '2017-01-05'  ,       -10),
    -> (126  ,  '2017-01-06'  ,       13);
Query OK, 13 rows affected (0.06 sec)
Records: 13  Duplicates: 0  Warnings: 0

MariaDB [sandbox]>
MariaDB [sandbox]>
MariaDB [sandbox]>
MariaDB [sandbox]> select s.userid,s.dt,s.amount,
    ->  case when s.crs is null then 0 else s.crs end crs,
    ->  case when s.exhaust is null then 0 else s.exhaust end exhaust,
    ->  case when s.amount > 0 and s.amount <= s.crs and s.crs > 0 then 'Fully paid'
    ->  when s.amount > 0 and s.amount > s.crs and s.crs > 0 then concat('Part paid -' ,s.crs)
    ->  else ''
    ->  end msg
    -> from
    -> (
    -> select t1.*,
    ->  if(t1.userid <> @p ,
    ->   @crs:=(select sum(t2.amount)  * - 1 from t t2 where t2.userid = t1.userid and t2.amount < 0)
    ->   ,@crs:=@crs)  crs,
    ->   if(t1.amount < 0 ,@crs:=@crs,if (t1.amount > @crs , @crs:=0,@crs:=@crs - t1.amount)) exhaust,
    ->   @p:=t1.userid p
    ->
    -> from  (select @p:=0,@crs:=0) p ,t t1
    -> order by t1.userid, t1.dt
    -> ) s
    -> ;
+--------+------------+--------+------+---------+--------------+
| userid | dt         | amount | crs  | exhaust | msg          |
+--------+------------+--------+------+---------+--------------+
|    123 | 2017-01-01 |      5 | 2    | 0       | Part paid -2 |
|    123 | 2017-01-03 |      2 | 0    | 0       |              |
|    123 | 2017-01-05 |     -2 | 0    | 0       |              |
|    124 | 2017-01-04 |      2 | 0    | 0       |              |
|    124 | 2017-01-04 |      3 | 0    | 0       |              |
|    125 | 2017-01-01 |      5 | 6    | 1       | Fully paid   |
|    125 | 2017-01-03 |      2 | 1    | 0       | Part paid -1 |
|    125 | 2017-01-05 |     -6 | 0    | 0       |              |
|    126 | 2017-01-01 |      5 | 20   | 15      | Fully paid   |
|    126 | 2017-01-02 |    -10 | 15   | 15      |              |
|    126 | 2017-01-03 |      2 | 15   | 13      | Fully paid   |
|    126 | 2017-01-05 |    -10 | 13   | 13      |              |
|    126 | 2017-01-06 |     13 | 13   | 0       | Fully paid   |
+--------+------------+--------+------+---------+--------------+
13 rows in set (0.03 sec)

注意:- 我还没有完全测试过!!

您需要一个联结点 table 来关联借方交易和贷方交易。像这样:

CREATE TABLE debit_credit_map (
  debit_id INT NOT NULL,
  credit_id INT NOT NULL,
  amount DECIMAL(14,2) NOT NULL,
  applied_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY(debit_id, credit_id),
  KEY(credit_id)
)ENGINE=InnoDB;

当你想对借记应用信用时,你首先要 post 信用,捕获它的 id,然后开始一个循环——验证有多少信用未应用(当然最初为零) ,通过选择总和(金额),其中 credit_id = 您正在处理的当前信用交易,从信用总额中减去。

然后在映射 table 中找到总和(金额)小于借方交易金额或根本没有条目的最早借方。

通过在新的 table cross-referencing 借方交易与贷方交易和应用的贷方金额中创建一行,将尽可能多的贷方应用于该交易反对借方。

重复此操作,直到信用额度用完或不再有未完全注资的借记交易。

使用此逻辑可以简单地将债务拆分为贷方或贷方拆分为借方,并为您提供我认为您正在寻找的东西。

借记是支付、部分支付还是全部支付取决于该交易的新 table 中的条目总和是否为 0、小于交易总额或等于交易总额。不要将该状态存储在交易本身上,而是按需计算它,左连接 table 并按 debit_id.

分组

无论借方和贷方存储在同一个 table 还是两个不同的 table 中,此方案都有效。根据需要在 ID 上添加外键。

奖励积分:

使用触发器来阻止删除和更新联结 table。这些条目代表无法撤消的静态历史事实。它们应该被认为是 immutable,否则你就是在改写历史。

在插入连接 table 时使用触发器来防止插入,与连接 table 中的现有相关行相比,插入会导致分配给当前借方的总金额或贷方超过借方或贷方的实际总额。这将阻止应用程序错误造成代价高昂的数学错误。