背景:

贷款相关行业的数据分析的人员,经常碰到这样一个情况:数据库里面有还款计划表和还款历史表,但是我要对历史上每天的逾期情况进行分析,即要生成历史每日逾期表(包含逾期本金、逾期利息、逾期费用、逾期天数、本次逾期起始日期等逾期信息)。这个问题我也碰到很多,基于还款计划表和还款历史表,理论上是可以推导出每日逾期情况的,但是仅仅写写 select from join on where 是很难实现的,每次都不了了之了。

缘由:

最近某个项目必须要生成这样一张表,不得不花点心思在这件事上。

理论:

通过分析,只写表关联是实现不了这个功能的。我把解决问题的希望寄托于存储过程,一个我见过,但是从来没有使用过的东西。尝试这样做之前,我并没有一个完整思路,我觉得这个事情能做基于两点:1、理论上给我一笔件的还款计划和还款历史,我是能知道这笔件每天的逾期情况的,即一定可以得到结果  2、在存储过程中见过while循环,我得出结论,存储过程可以有复杂的流程控制,即类似于java里面的if else/continue/break等控制流程语句(我是个Java程序兼职做数据分析)。

实战:

数据说明

--放款表dumiao_loan_info_true

CREATE TABLE `dumiao_loan_info_true` (
  `loan_bill_no` varchar(255) DEFAULT NULL, #借据号
  `loan_date` varchar(10) DEFAULT NULL, #放款日期
  KEY `loan_bill_no` (`loan_bill_no`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

--还款计划表dumiao_repay_plan_true

CREATE TABLE `dumiao_repay_plan_true` (
  `app_no` varchar(255) DEFAULT NULL, #申请号
  `loan_bill_no` varchar(255) DEFAULT NULL, #借据号
  `loan_prin` varchar(255) DEFAULT NULL, #放款本金
  `loan_attribute` varchar(255) DEFAULT NULL,#总期数
  `curr_term` varchar(255) DEFAULT NULL,#当期期数
  `loan_term_prin` varchar(255) DEFAULT NULL,#当期本金
  `loan_term_fee` varchar(255) DEFAULT NULL,#当期费用
  `loan_term_int` varchar(255) DEFAULT NULL,#当期利息
  `account_fee` int(1) NOT NULL DEFAULT '0',#账户管理费
  `loan_pmt_due_date` varchar(10) CHARACTER SET utf8mb4 DEFAULT NULL,#当期到期日
  KEY `loan_bill_no_term` (`loan_bill_no`,`curr_term`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

--还款历史表dumiao_repay_actual_true

CREATE TABLE `dumiao_repay_actual_true` (
  `data_date` varchar(10) DEFAULT NULL,#数据日期
  `app_no` varchar(255) DEFAULT NULL,#申请号
  `loan_bill_no` varchar(255) DEFAULT NULL,#借据号
  `repay_date` varchar(10) DEFAULT NULL,#还款日期
  `principal` double DEFAULT NULL,#还款本金
  `interest` double DEFAULT NULL,#还款利息
  `fee` double DEFAULT NULL,#还款利息
  `penalty` int(1) NOT NULL DEFAULT '0',#罚息
  `compound` int(1) NOT NULL DEFAULT '0',#复利
  `late_fee` int(1) NOT NULL DEFAULT '0',#滞纳金
  `prepay_fee` int(1) NOT NULL DEFAULT '0',#提前还款手续费
  `pay_account_fee` int(1) NOT NULL DEFAULT '0',#账户管理费
  KEY `loan_bill_no` (`loan_bill_no`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

--历史每日逾期表dumiao_repay_overdue_true  (无数据,表结构建好,往里面插入数据)

CREATE TABLE `dumiao_repay_overdue_true` (
  `data_date` date DEFAULT NULL COMMENT '数据日期即历史日期',
  `app_no` varchar(100) DEFAULT NULL COMMENT '申请编号',
  `loan_bill_no` varchar(100) DEFAULT NULL COMMENT '贷款借据号',
  `overdue_days` int(10) DEFAULT NULL COMMENT '逾期天数',
  `overdue_prin` decimal(20,2) DEFAULT NULL COMMENT '逾期本金',
  `overdue_fee` decimal(20,2) DEFAULT NULL COMMENT '逾期手续费',
  `overdue_int` decimal(20,2) DEFAULT NULL COMMENT '逾期利息',
  `overdue_account_fee` decimal(20,2) DEFAULT NULL COMMENT '逾期账户管理费',
  `overdue_penalty` decimal(20,2) DEFAULT NULL COMMENT '罚息',
  `overdue_compound` decimal(20,2) DEFAULT NULL COMMENT '复利',
  `overdue_late_fee` decimal(20,2) DEFAULT NULL COMMENT '滞纳金',
  `overduer_date` date DEFAULT NULL COMMENT '逾期日期'
) ENGINE=InnoDB DEFAULT CHARSET=utf8

一、思路:

首先整理了一下思理(思路我是边编码边整理的,因为存储过程的语法要找度娘):

1、遍历每个历史日期(以下称current_data_date)(格式yyyy-mm-dd,如2019-06-10),从2019-06-10到2019-09-30

2、该个历史日期current_data_date的之前已经发生的放款,遍历每笔放款

3、“截止到当天current_data_date之前的还款计划本金求和”  减去 “截止到当天current_data_date之前的还款计划本金求和”,即差额diff = sum(plan_prin) - sum(repay_prin) 大于0,即还有到期未还款本金,这就是逾期了。 (我这边做了一个容错,因为该项目的还款计划没有提供,还款计划也是我根据等额本息生成的,可能有分的差异,为了保险起见,我设置了容错10)

4、确定该笔是逾期之后,接下来确认当前的逾期是从哪一期开始逾期的。(前提:还款是从前还到后,不能跳)

--a、从最近的一期开始,如果最近一期还款计划的本金能覆盖 差额diff  ,则说明是从最近一期开始逾期

--b、如果最近一期还款计划的本金不能覆盖 差额diff  ,则要包含最近两期

--c、以此类推,找出从哪一期开始,还款计划的本金能覆盖  差额diff

5、计算相关数值,插入dumiao_repay_overdue_true,结束这笔的计算。

二、代码

写了两个存储过程

1、pro_certain_day_overdue

create PROCEDURE pro_certain_day_overdue(in current_data_date varchar(10))

传入你要计算哪一天的逾期情况,格式 %Y-%m-%d yyyy-mm-dd,计算出该天的逾期情况。

存储过程建立代码:

drop PROCEDURE  if EXISTS pro_certain_day_overdue;
create PROCEDURE pro_certain_day_overdue(in current_data_date varchar(10))
BEGINdeclare stop_flag int default 0;#游标遍历完的标识declare lbn varchar(100) default 'loan_bill_no'; #当前遍历的借据号declare ld varchar(10) default 'loan_date'; #当前借据的放款日期declare curr_total_plan_prin DECIMAL(14,2) default 0.00; #总还款计划本金(站在历史日期去看,已经经历的,下同)declare curr_total_plan_fee DECIMAL(14,2) default 0.00; #总还款计划费用declare curr_total_plan_int DECIMAL(14,2) default 0.00; #总还款计划利息declare curr_total_hst_prin DECIMAL(14,2) default 0.00; #总还款历史本金declare curr_total_hst_fee DECIMAL(14,2) default 0.00; #总还款历史费用declare curr_total_hst_int DECIMAL(14,2) default 0.00; #总还款历史利息declare diff_plan_hst DECIMAL(14,2) default 0.00; #总还款计划本金-总还款历史本金declare diff_plan_hst_fee DECIMAL(14,2) default 0.00; #总还款计划费用-总还款历史费用declare diff_plan_hst_int DECIMAL(14,2) default 0.00; #总还款计划费用-总还款历史费用declare curr_plan_count int default 0; #还款计划期数declare acum_count int default 1; #计数器,最近acum_count期declare acum_count_less_one int default 0;#比acum_count小1,方便使用,别问我为什么不直接使用acum_count,过程比较艰辛declare sum_prin_terms_selected DECIMAL(14,2) default 0.00;#最近acum_count期的还款计划本金declare curr_overdue_date varchar(20) default '1990-01-01';#当前最近一个计划还款日DECLARE cur_loan CURSOR FOR select loan_bill_no,loan_date from dumiao_loan_info_true where loan_date<=date_Format(adddate(str_to_date(current_data_date,'%Y-%m-%d'),INTERVAL -1 month),'%Y-%m-%d');#查出说有件,游标遍历DECLARE CONTINUE HANDLER FOR NOT FOUND SET stop_flag = 1;#游标遍历完,把标识设为1open cur_loan;#打开游标while stop_flag<>1 DOFETCH cur_loan INTO lbn,ld;#遍历取件select coalesce(sum(loan_term_prin),0),coalesce(sum(loan_term_fee),0),coalesce(sum(loan_term_int),0) into curr_total_plan_prin,curr_total_plan_fee,curr_total_plan_int from dumiao_repay_plan_true where loan_bill_no = lbn and loan_pmt_due_date <= current_data_date;#赋值总还款计划相关select coalesce(sum(principal),0),coalesce(sum(fee),0),coalesce(sum(interest),0) into curr_total_hst_prin,curr_total_hst_fee,curr_total_hst_int from dumiao_repay_actual_true where loan_bill_no = lbn and repay_date <= current_data_date; #赋值总还款历史相关set diff_plan_hst = curr_total_plan_prin-curr_total_hst_prin;#赋值  总还款计划本金-总还款历史本金set diff_plan_hst_fee = curr_total_plan_fee-curr_total_hst_fee;#赋值  总还款计划费用-总还款历史费用set diff_plan_hst_int = curr_total_plan_int-curr_total_hst_int;#赋值  总还款计划费用-总还款历史费用set acum_count_less_one = 0;#赋值 循环search_loop开始前置为0,放在search_loop结束后亦可if diff_plan_hst > 10 THEN #10是一个”容错“,因为我还款计划是手动生成的,有分的差异,10写成1也可以。如果严格来讲应该是0,即diff_plan_hst >= 0select count(1) into curr_plan_count from dumiao_repay_plan_true where loan_bill_no = lbn and loan_pmt_due_date <= current_data_date;#赋值 还款计划期数search_loop:while acum_count <= curr_plan_count DO #开始遍历近1期,近2期,...,近curr_plan_count期if acum_count >= curr_plan_count THEN #如果已经到了包含所有期数(当前日期current_data_date之前的),不用判断了,第一期的到期日就是逾期开始日,即 当前最近一个计划还款日 往前推acum_count-1个月,即往前推acum_count_less_one个月select max(loan_pmt_due_date) into curr_overdue_date from dumiao_repay_plan_true where loan_bill_no = lbn and loan_pmt_due_date <= current_data_date  ;#赋值 当前最近一个计划还款日INSERT INTO dumiao_repay_overdue_true(`data_date`, `app_no`, `loan_bill_no`, `overdue_days`, `overdue_prin`, `overdue_fee`, `overdue_int`, `overdue_account_fee`, `overdue_penalty`, `overdue_compound`, `overdue_late_fee`, `overduer_date`) VALUES (current_data_date, lbn, lbn, DATEDIFF(current_data_date,adddate(curr_overdue_date,interval -acum_count_less_one month)) + 1, diff_plan_hst, diff_plan_hst_fee, diff_plan_hst_int, '0.00', '0.00', '0.00', '0.00', adddate(curr_overdue_date,interval -acum_count_less_one month));#关键点: -acum_count_less_one  代表往前推acum_count_less_one个月LEAVE search_loop;#近acum_count已经覆盖,结束search_loop循环, 相当于Java的breakend IF;select sum(loan_term_prin) into sum_prin_terms_selected from dumiao_repay_plan_true where loan_bill_no = lbn and loan_pmt_due_date <= current_data_date and loan_pmt_due_date > adddate(current_data_date,interval -acum_count month); #近acum_count期的还款计划本金if sum_prin_terms_selected > diff_plan_hst - 10 THEN #近acum_count期的还款计划本金 如果能覆盖 逾期金额,则当前的选出来的最远的那期的计划还款日就是逾期开始日期,即 当前最近一个计划还款日 往前推acum_count_less_one个月  select max(loan_pmt_due_date) into curr_overdue_date from dumiao_repay_plan_true where loan_bill_no = lbn and loan_pmt_due_date <= current_data_date  ;#赋值 当前最近一个计划还款日INSERT INTO dumiao_repay_overdue_true(`data_date`, `app_no`, `loan_bill_no`, `overdue_days`, `overdue_prin`, `overdue_fee`, `overdue_int`, `overdue_account_fee`, `overdue_penalty`, `overdue_compound`, `overdue_late_fee`, `overduer_date`) VALUES (current_data_date, lbn, lbn, DATEDIFF(current_data_date,adddate(curr_overdue_date,interval -acum_count_less_one month)) + 1, diff_plan_hst, diff_plan_hst_fee, diff_plan_hst_int, '0.00', '0.00', '0.00', '0.00', adddate(curr_overdue_date,interval -acum_count_less_one month));#关键点: -acum_count_less_one  代表往前推acum_count_less_one个月LEAVE search_loop;#近acum_count已经覆盖,结束search_loop循环,相当于Java的breakend IF;set acum_count = acum_count + 1;#计数器+1,例如 2变3,相当于近2期 变到 包含近3期set acum_count_less_one = acum_count - 1; #一直比acum_count小1end while search_loop;set curr_plan_count = 0; #该笔结束,初始化set acum_count = 1; #该笔结束,初始化set sum_prin_terms_selected = 0.00; #该笔结束,初始化set curr_overdue_date = '1990-01-01'; #该笔结束,初始化end if;set curr_total_plan_prin = 0.00; #该笔结束,初始化set curr_total_hst_prin = 0.00; #该笔结束,初始化set diff_plan_hst = 0.00; #该笔结束,初始化end while;close cur_loan;#关闭游标set current_data_date = DATE_FORMAT(ADDDATE(STR_TO_DATE(current_data_date,'%Y-%m-%d'),INTERVAL 1 DAY),'%Y-%m-%d');#不用管这句,可以去掉,之前写在一起了
end
;

调用示例:

call pro_certain_day_overdue('2019-07-14');

2、pro_all_day_overdue_from_to

create PROCEDURE pro_all_day_overdue_from_to(in start_date varchar(10),in end_date varchar(10))

传入你要计算的范围,算头算尾。此过程调用pro_certain_day_overdue

drop PROCEDURE  if EXISTS pro_all_day_overdue_from_to;
create PROCEDURE pro_all_day_overdue_from_to(in start_date varchar(10),in end_date varchar(10))
BEGINdeclare current_data_date varchar(10) default start_date;while current_data_date <= end_date docall pro_certain_day_overdue(current_data_date);#调用当日的逾期存储过程set current_data_date = DATE_FORMAT(ADDDATE(STR_TO_DATE(current_data_date,'%Y-%m-%d'),INTERVAL 1 DAY),'%Y-%m-%d');#日期+1天end while;
end
;

调用示例:

call pro_all_day_overdue_from_to('2019-06-10', '2019-09-30') ;

三、碰到的问题总结

1、while 里面不能decalre声明变量,这也是为什么我要写那么多初始化语句,这点跟java很不一样。

2、如果while里面要decalre声明变量,提取成另外一个存储过程,然后调用该存储过程。

3、游标的结束hanlder要放在声明游标之后。

4、每一条语句都以;结束

5、查询记得加索引,效率相差很大

6、注意mysql的执行顺序,limit 和聚合(即select),被坑的很惨。我本来有用limit的,后来换成另外一种写法。

7、limit a,b  a和b只能是单个的变量或者数字,不能有运算符,例如 a-1,会报错

8、可以用select 变量名; 调试

9、其他未弄明白的问题:存储过程嵌套游标、使用子查询等

总的来说,收获还是蛮大的,写下这篇博客,做个记录,也给其他小伙伴做个参考。

由还款计划表和还款历史表,生成历史每日逾期表_历史逾期情况回溯_Mysql存储过程实现相关推荐

  1. 通过还款计划表监控还款异常

    还款计划的相关监控是整个信贷中非常核心的关键指标,通过还款计划的观测我们不单单能了解客户的还款情况,还能通过还款计划表来做一些反欺诈的监控,这个具体是怎么落地的呢? 今天来看看由番茄风控-<全线 ...

  2. 根据Oracle数据库已存在的表生成其他数据库建表语句

    文章目录 写这个Demo的出发点 Demo中还存在的问题 期望将来能实现的 生成的建表语句 源代码 写这个Demo的出发点 入职刚半年的菜鸟一枚,公司项目需支持Oracle.Mysql.DB2三个库, ...

  3. Power query(Power BI) 自动生成贷款公司的还款计划表

    小李在一家经营车辆贷款公司上班,这家车辆贷款公司名下有几十个分子公司,每家公司都在卖车,并且经营贷款业务.小李需要在卖出车的第一时间做一份还款计划表给客户(下图),并且自己需要保存一份,以便每个月核对 ...

  4. 解决Activiti 7自动部署后不生成数据库act_hi _* 历史表

    使用Activiti 7时,启动项目会自动部署生成数据库表,但在默认配置下会发现数据库中没有act_hi _* 历史表,此时我们在配置文件中加上以下配置即可: spring:activiti:db-h ...

  5. 按揭贷款等额本金还款计算公式(可以自己修改数据计算出自己的还款计划表)...

    比如我的还款计划表2009年为: -------------------2009年还款计划------------------- 2009年第01月应还款金额 : 1583.33 + 1272.81 ...

  6. 大数据风控必看,挖掘学历数据中暗藏的还款意愿及还款能力

    市场上80%以上的信贷产品或信用卡,在申请人填写基本信息时都会需要填写学历情况,银行信用卡部门还会根据学历等级来设置进件门槛及额度标准. 那学历情况对于消费金融行业风控部门设置进件门槛或风控规则权重处 ...

  7. 【风控】如何判断用户的还款能力和还款意愿?

    还款能力决定了审批额度,还款意愿决定了能否审批通过.在贷款.信用卡申请中金融机构会对所有申请人做风控评估,其最主要目的就是评估申请人的还款能力和还款意愿,根据评估结果再给出审批额度. 那么金融机构是如 ...

  8. Idea groovy表生成实体类带注释

    Idea groovy表生成实体类带注释 1.点开datasourse,打开idea带的数据库工具,具体添加数据库连接,这里不描述. 这时点击会生成一个poji 这时生成的pojo中是不带中文注释的, ...

  9. 点滴积累【C#】---检验编号在本表中自动生成,与其他表无关

    检验编号在本表中自动生成,与其他表无关 效果: 描述:在本表中自动生成编号,与其他表无关. 调用: 1 protected void Page_Load(object sender, EventArg ...

最新文章

  1. Eclipse设置条件断点
  2. hdu 6852Path6(最短路+最小割)
  3. 多媒体基础:动画和视频知识笔记
  4. 《Python Cookbook 3rd》笔记(3.12):基本的日期与时间转换
  5. python金字塔_高斯金字塔与拉普拉斯金字塔的原理与python构建
  6. 哈工大理论力学第八版电子版_校史上的这些天(37)| 和你一起在“岁月”中读懂哈工大...
  7. Qt学习笔记-基于QGraphicsScene的打地鼠游戏
  8. 2018-12-25 上机作业
  9. Linux下的实时流媒体编程
  10. 初识java atomic
  11. redis 主从不同步连接不上
  12. 硬件算法与软件算法实现区别通俗易懂
  13. 服务器硬盘开机吱吱响,开机时硬盘吱吱响的原因
  14. TI CC1310 sub1G的SDK开发之入门
  15. 写bug的日常——KeyError错误原因
  16. Spring学习笔记(五):JDBCTemplate+事务管理
  17. Rule of lawlessness 南非法治之战 | 经济学人中英双语对照精读笔记
  18. 网易互娱2017在线笔试——题目一:电子数字
  19. 双非渣本小Android四年磨一剑,秋招大厂(字节、腾讯、B站)面经分享
  20. parcelable接口实现

热门文章

  1. 今日头条的排名算法_今日头条核心技术“个性推荐算法”揭秘
  2. 计算机术语awage表示什么,计量经济学第3章计算机习题
  3. 如何利用PDF转CAD转换器转换文件格式
  4. windows7的无线互联
  5. android 判断有无sim卡,Android判断手机里是否有SIM卡
  6. 嵌入式——使用定时器输出PWM波形,实现 LED呼吸灯的效果
  7. STM32使用延时控制LED灯亮暗变换,LED呼吸灯效果
  8. 局域计算机网络,计算机网络( 局域网组建).doc
  9. uniapp微信小程序分享后,点击进入分享页面无法回到首页
  10. 五步法,做有用的经营分析