首先让我解释表结构和我所需的输出。
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
由于
答案 0 :(得分:1)
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)
注意: - 我还没有完全测试过这个!!
答案 1 :(得分:1)
您需要一个联结表来关联借记交易和信用交易。像这样:
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;
当您想要借记借方时,首先发布信用额度,获取其ID,然后开始循环 - 通过选择金额(金额)来验证未应用的信用额度(当然最初为零) )其中credit_id =您正在处理的当前信用交易,从信用总额中减去。
然后在地图表中找到一笔金额最小的借记(金额),该金额小于借记交易金额,或根本没有条目。
通过在新表中创建一个交叉引用借方交易的行与信用交易以及针对借记的贷记金额,将适当的信用额应用于该交易。
重复信用,直到信用卡用完或没有更多借记交易尚未完全获得资金。
使用这种逻辑可以简单地将债务或债券之间的债务分摊,并为您提供我认为您正在寻找的内容。
借记是付款,部分还是已付款的回答是新交易表中该条目的条目总和是0,小于交易总额还是等于交易总额。不要将该状态存储在事务本身上,而是按需计算它,左边是通过debit_id连接此表和组。
此方案适用于借方和贷方是存储在同一个表中,还是存储在两个不同的表中。根据需要在ID上添加外键。
奖励积分:
使用触发器阻止对联结表的删除和更新。这些条目代表无法撤消的静态历史事实。它们应该被认为是不可变的,因为否则你就是在重写历史。
在插入表中使用触发器以防止插入,与联结表中的现有相关行相比,这将导致分配给当前借记或贷记的总金额超过实际借方总金额或信用。这将阻止应用程序错误导致潜在的代价高昂的数学错误。