原标题:解决报表sql中的累计收益率问题?换个姿势,再来一次~

最近在做券商资产分析业务的时候,碰到个报表需求,要求计算从20170301到20170831期间,大约40w客户(表数据量5000w)的按天累计收益率的报表。

计算公式如下:

累计收益率=(1+上日累计收益率)*(1+当日收益率)-1

小知识:累积收益率不仅可以按天分割累积,如果想要更精确,还可以按小时累积,如果是很粗略的计算,也可以按月累计。

好的设计算法,应该是从第一天开始,每天都算一次到当天的累计收益率,然后想算到哪天,直接跟一个where条件busi_date就好,然鹅这种方法我们必须拍一个起始值,且如果碰到产品经理突发奇想,拍脑袋定义了一个日期,从该日期算起,就两眼一懵了。

当日收益率是存在于我们的表中,我将其称为原材料数据。结合上面的计算公式,这就很像一个乘法的斐波那契算法,每一天的累计收益率,都是需要上一天的累计收益率(第一天的累计收益率,我们定为0)。于是脑海中想到一个恐怖的词汇:

递归

姿势一:pl/sql匿名块

代码:略,思路见优点,这里就不展开了

优点:可控,我们可以按天loop循环计算,每计算一天以后,将结果输出到一张表中,便于我们估算运算进度和运行时间。

缺点:过多的上下文切换,性能对比一条sql语句来说,较差。也不适用"能用sql搞定,就不要用plsql"的itpub的信条(笑)

姿势二:sql(递归with)

这里我截取最主要的部分代码(整体代码超过300行),直接把tmp1视图当作实体表即可,我也做了

实体化hint。重点是tmp2,这里没有选择使用connect by做递归是考虑到数学公式中的连乘需求,选择了oracle的CTE递归。

知识点引申:oracle支持connect by,CTE递归;mssql支持cte递归,不支持connect by;dm支持connect by,正在支持cte递归;

sqllite支持cte递归,不支持connect by;mysql貌似都不支持,只能用存储过程替代。

代码及计划:

withtmp1as

(select/*+PARALLEL(a 8) materialize*/a.busi_date,

case

whena.ord_dybgn_tot_net_ast + a.crd_dybgn_tot_net_ast +

nvl(b.ord_time_cptl_flw_ent,0) +

nvl(b.crd_time_cptl_flw_ent,0) <>0then

(a.ord_d_prft_amt + a.crd_d_prft_amt) /

(a.ord_dybgn_tot_net_ast + a.crd_dybgn_tot_net_ast +

nvl(b.ord_time_cptl_flw_ent,0) +

nvl(b.crd_time_cptl_flw_ent,0)) *100

else

0

endasprft_rto_100,

case

whena.ord_dybgn_tot_net_ast + a.crd_dybgn_tot_net_ast +

nvl(b.ord_time_cptl_flw_ent,0) +

nvl(b.crd_time_cptl_flw_ent,0) <>0then

(a.ord_d_prft_amt + a.crd_d_prft_amt) /

(a.ord_dybgn_tot_net_ast + a.crd_dybgn_tot_net_ast +

nvl(b.ord_time_cptl_flw_ent,0) +

nvl(b.crd_time_cptl_flw_ent,0))

else

0

endasprft_rto,

a.client_id,

row_number() over(partitionbya.client_id orderbya.busi_date) rn

from(select*

fromddw.t_ddw_f21_c_d_ast_prft_n

wherebusi_date >='20170301'

andbusi_date <='20170831') a

left join ddw.t_ddw_f21_c_d_ast_prft_n_tmp b

on a.client_id = b.client_id

anda.busi_date = b.busi_date

orderbybusi_date),

tmp2(busi_date,

prft_rto_100,

prft_rto,

client_id,

rn,

lev)as

(selectbusi_date,0,0, client_id, rn,1lev

fromtmp1 t1

wherern =1

unionall

selectt1.busi_date,

t1.prft_rto_100,

(t2.prft_rto +1) * (t1.prft_rto +1) -1,

t1.client_id,

t1.rn,

t1.rn +1lev

fromtmp2 t2, tmp1 t1

wheret2.rn = t1.rn -1

andt1.client_id = t2.client_id),

tmp3(busi_date,

prft_rto,

gt_prft_rto,

client_id)as

(selectbusi_date, prft_rto_100, prft_rto *100, client_id

fromtmp2

wherebusi_date ='20170831')

selectcount(*)fromtmp3;

执行计划:

PlanHashValue:2806335166

------------------------------------------------------------------------------------------------------------------------------------

|Id|Operation|Name|Rows|Bytes|Cost|Time|

------------------------------------------------------------------------------------------------------------------------------------

|0| SELECT STATEMENT | |1|7|285527|00:57:07|

|1| TEMP TABLE TRANSFORMATION | | | | | |

|2| PX COORDINATOR | | | | | |

|3| PX SEND QC (RANDOM) | :TQ10003 |54970304|2968396416|239895|00:47:59|

|4| LOAD AS SELECT | SYS_TEMP_0FD9D7335_BF134D9 | | | | |

|5| SORT ORDER BY | |54970304|2968396416|239895|00:47:59|

|6| PX RECEIVE | |54970304|2968396416|239895|00:47:59|

|7| PX SEND RANGE | :TQ10002 |54970304|2968396416|239895|00:47:59|

|8| WINDOW SORT | |54970304|2968396416|239895|00:47:59|

|9| PX RECEIVE | |54970304|2968396416|37635|00:07:32|

|10| PX SEND HASH | :TQ10001 |54970304|2968396416|37635|00:07:32|

| *11| HASH JOIN RIGHT OUTER | |54970304|2968396416|37635|00:07:32|

|12| BUFFER SORT | | | | | |

|13| PX RECEIVE | |235907|5425861|274|00:00:04|

|14| PX SEND PARTITION (KEY) | :TQ10000 |235907|5425861|274|00:00:04|

| *15| TABLE ACCESS FULL | T_DDW_F21_C_D_AST_PRFT_N_TMP |235907|5425861|274|00:00:04|

|16| PX PARTITION RANGE ITERATOR | |54970304|1704079424|37343|00:07:29|

| *17| TABLE ACCESS FULL | T_DDW_F21_C_D_AST_PRFT_N |54970304|1704079424|37343|00:07:29|

|18| SORT AGGREGATE | |1|7| | |

| *19| VIEW | |131159749|918118243|45632|00:09:08|

|20| UNION ALL (RECURSIVE WITH) BREADTH FIRST | | | | | |

|21| PX COORDINATOR | | | | | |

|22| PX SEND QC (RANDOM) | :TQ20000 |54970304|1759049728|15192|00:03:03|

| *23| VIEW | |54970304|1759049728|15192|00:03:03|

|24| PX BLOCK ITERATOR | |54970304|2968396416|15192|00:03:03|

|25| TABLE ACCESS FULL | SYS_TEMP_0FD9D7335_BF134D9 |54970304|2968396416|15192|00:03:03|

| *26| HASH JOIN | |76189445|6323723935|30439|00:06:06|

|27| RECURSIVE WITH PUMP | | | | | |

|28| VIEW | |54970304|2473663680|15192|00:03:03|

|29| TABLE ACCESS FULL | SYS_TEMP_0FD9D7335_BF134D9 |54970304|2968396416|15192|00:03:03|

------------------------------------------------------------------------------------------------------------------------------------

PredicateInformation(identifiedbyoperation id):

------------------------------------------

*11- access("T_DDW_F21_C_D_AST_PRFT_N"."BUSI_DATE"="B"."BUSI_DATE"(+) AND"T_DDW_F21_C_D_AST_PRFT_N"."CLIENT_ID"="B"."CLIENT_ID"(+))

*15- filter("B"."BUSI_DATE"(+)>='20170301'AND"B"."BUSI_DATE"(+)<='20170831')

*17- filter("BUSI_DATE"<='20170831')

*19- filter("BUSI_DATE"='20170831')

*23- filter("RN"=1)

*26- access("T2"."RN"="T1"."RN"-1AND"T1"."CLIENT_ID"="T2"."CLIENT_ID")

Note

-----

-dynamicsampling usedforthisstatement

注意执行计划的20-29行,理论上这里是进行了多次表的自连接,由于数据量巨大,虽然有分区,连接方式也是hash join

优点;减少了plsql中的上下文切换,理论上应该比plsql性能高

缺点:时间不可控,全靠奴家的预估。传单个客户问题不大。单个客户运行时间大约是100~200ms。40w的话。。。我笔记本开了一晚,最后运行报错,这要是从晚上跑到交易时间,一口黑锅盖死你。

姿势三:姿势三是在姿势二的基础上优化而来。

我们考虑到每天的累计是靠前一天的结果计算而来。11g的cte姿势都玩出来了,10g的model岂不是更合适

代码及其计划:

withtmp1as

(select/*+PARALLEL(a 8) materialize*/a.busi_date,

case

whena.ord_dybgn_tot_net_ast + a.crd_dybgn_tot_net_ast +

nvl(b.ord_time_cptl_flw_ent,0) +

nvl(b.crd_time_cptl_flw_ent,0) <>0then

(a.ord_d_prft_amt + a.crd_d_prft_amt) /

(a.ord_dybgn_tot_net_ast + a.crd_dybgn_tot_net_ast +

nvl(b.ord_time_cptl_flw_ent,0) +

nvl(b.crd_time_cptl_flw_ent,0)) *100

else

0

endasprft_rto_100,

case

whena.ord_dybgn_tot_net_ast + a.crd_dybgn_tot_net_ast +

nvl(b.ord_time_cptl_flw_ent,0) +

nvl(b.crd_time_cptl_flw_ent,0) <>0then

(a.ord_d_prft_amt + a.crd_d_prft_amt) /

(a.ord_dybgn_tot_net_ast + a.crd_dybgn_tot_net_ast +

nvl(b.ord_time_cptl_flw_ent,0) +

nvl(b.crd_time_cptl_flw_ent,0))

else

0

endasprft_rto,

a.client_id,

row_number() over(partitionbya.client_id orderbya.busi_date) rn

from(select*

fromddw.t_ddw_f21_c_d_ast_prft_n

wherebusi_date >='20170301'

andbusi_date <='20170831') a

left join ddw.t_ddw_f21_c_d_ast_prft_n_tmp b

on a.client_id = b.client_id

anda.busi_date = b.busi_date

)

select*

from(selectclient_id,

rn,

busi_date,

prft_rto_100,

gt_prft_rto *100gt_prft_rto

fromtmp1 model partitionby(client_id) dimensionby(rn) measures(busi_date, prft_rto_100, prft_rto,0gt_prft_rto) rules update(gt_prft_rto [ any ] orderbyrn =case

whencv(rn) =1then

0

else

(prft_rto [

cv(rn)

] +1) *

(gt_prft_rto [

cv(rn) -1

] +1) -1

end)

orderbyclient_id, rn)

wherebusi_date ='20170831'

执行计划:

PlanHashValue:890671388

---------------------------------------------------------------------------------------------------------------------------

|Id|Operation|Name|Rows|Bytes|Cost|Time|

---------------------------------------------------------------------------------------------------------------------------

|0| SELECT STATEMENT | |54970304|3188277632|260856|00:52:11|

|1| TEMP TABLE TRANSFORMATION | | | | | |

|2| PX COORDINATOR | | | | | |

|3| PX SEND QC (RANDOM) | :TQ10002 |54970304|2968396416|138765|00:27:46|

|4| LOAD AS SELECT | SYS_TEMP_0FD9D7337_BF134D9 | | | | |

|5| WINDOW SORT | |54970304|2968396416|138765|00:27:46|

|6| PX RECEIVE | |54970304|2968396416|37635|00:07:32|

|7| PX SEND HASH | :TQ10001 |54970304|2968396416|37635|00:07:32|

| *8| HASH JOIN RIGHT OUTER | |54970304|2968396416|37635|00:07:32|

|9| BUFFER SORT | | | | | |

|10| PX RECEIVE | |235907|5425861|274|00:00:04|

|11| PX SEND PARTITION (KEY) | :TQ10000 |235907|5425861|274|00:00:04|

| *12| TABLE ACCESS FULL | T_DDW_F21_C_D_AST_PRFT_N_TMP |235907|5425861|274|00:00:04|

|13| PX PARTITION RANGE ITERATOR | |54970304|1704079424|37343|00:07:29|

| *14| TABLE ACCESS FULL | T_DDW_F21_C_D_AST_PRFT_N |54970304|1704079424|37343|00:07:29|

|15| PX COORDINATOR | | | | | |

|16| PX SEND QC (ORDER) | :TQ20001 |54970304|3188277632|122090|00:24:26|

| *17| VIEW | |54970304|3188277632|122090|00:24:26|

|18| SORT ORDER BY | |54970304|3188277632|122090|00:24:26|

|19| SQL MODEL ORDERED | |54970304|3188277632|122090|00:24:26|

|20| PX RECEIVE | |54970304|3188277632|15192|00:03:03|

|21| PX SEND RANGE | :TQ20000 |54970304|3188277632|15192|00:03:03|

|22| VIEW | |54970304|3188277632|15192|00:03:03|

|23| PX BLOCK ITERATOR | |54970304|2968396416|15192|00:03:03|

|24| TABLE ACCESS FULL | SYS_TEMP_0FD9D7337_BF134D9 |54970304|2968396416|15192|00:03:03|

---------------------------------------------------------------------------------------------------------------------------

PredicateInformation(identifiedbyoperation id):

------------------------------------------

*8- access("T_DDW_F21_C_D_AST_PRFT_N"."BUSI_DATE"="B"."BUSI_DATE"(+) AND"T_DDW_F21_C_D_AST_PRFT_N"."CLIENT_ID"="B"."CLIENT_ID"(+))

*12- filter("B"."BUSI_DATE"(+)>='20170301'AND"B"."BUSI_DATE"(+)<='20170831')

*14- filter("BUSI_DATE"<='20170831')

*17- filter("BUSI_DATE"='20170831')

Note

-----

-dynamicsampling usedforthisstatement

注意执行计划的19~24行,计划中tmp1的临时表只用到了一次,个人理解,model的语法使得我们在不用自连接就能实现表格,因为model拥有了跨行应用的能力

优点;减少了plsql中的上下文切换,最终不到4分钟就运行出了结果,皆大欢喜。

缺点:model语句学习成本,可读性较差(其实个人觉得还好)

姿势四:数学无敌。

因为累计相乘最后是可以转化为一种加法运算,转换为加法以后,我们的累计收益率可以用最熟悉的姿势sum() over()分析函数累加得到。

宗旨:累乘=》累加,具体算法是同行友商的算法,我估摸着大概是用到了如下的方法

with tmp1 as(select level rn from dual connect by level<=4)

select exp(sum(ln(rn)) over(order by rn))from tmp1

通过数学方法把1*2*3*4通过指对数算法把累乘转换为累加(累除也可以)

SACC2017 讲师演讲PPT合集 现已开放下载

后台回复 SACC2017

责任编辑:

oracle求累积收益率,解决报表sql中的累计收益率问题?换个姿势,再来一次~相关推荐

  1. Oracle数据库第四课——PL/SQL中的条件控制

    知识点: PL/SQL 有 3 种类型的条件控制结构:IF.ELSIF 和 CASE 语句.掌握 IF 语句的用法, 掌握 ELSIF 语句的用法, 理解嵌套 IF 语句的用法, 掌握 CASE 语句 ...

  2. c# 经验谈:巧用Expression表达式 解决类似于sql中 select in 的查询(适合于中小型项目)...

    我们在项目经常会碰到一些特殊需求 例如下拉框是复选的,查询条件是根据下拉框中复选项进行拼接 看到此图后大家肯定会说,这很简单嘛 将所有的选项 拼成"'1-3','5-9'"  然后 ...

  3. oracle 偶数与奇数,在PL / SQL中计算数字中的奇数和偶数

    我们给定一个正整数数字,任务是使用PL / SQL计算数字中奇数和偶数的计数. PL / SQL是SQL与编程语言的过程功能的组合.它是由Oracle Corporation在90年代初开发的,目的是 ...

  4. oracle date 隐式转换,PL/SQL中的数据类型隐式转换规则

    1) During INSERT and UPDATE operations, Oracle converts the value to the datatype of the affected co ...

  5. oracle 求一年多少天,SQL 计算一年有多少天

    SQL 计算一年有多少天,计算当前年份有多少天. SQL 计算一年有多少天 问题描述 计算当前年份有多少天. SQL 计算一年有多少天 解决方案 计算当前年份有多少天,等同于计算下一年的第一天和当前年 ...

  6. Oracle ——如何确定性能差的 SQL

    http://www.toadworld.com/KNOWLEDGE/KnowledgeXpertforOracle/tabid/648/TopicID/TSQ7/Default.aspx 本文主要说 ...

  7. 获取股票数据【实时更新股票数据、创建你的股票数据】、计算交易指标【买入、卖出信号、计算持仓收益、计算累计收益率】

    在上一次获取股票数据[使用JQData查询行情数据.财务指标.估值指标]学习了使用JQData来查询股票相关数据, 这次则开始一点点构建咱们的量化交易系统了. 量化交易平台功能模块了解: 对于一个量化 ...

  8. oracle数据源的报表sql计算慢解决

    http://blog.csdn.net/u012388497/article/details/17217705 问题描述 项目里有些报表出来的速度特别慢,尽管对润乾报表和Oracle数据库做了很多优 ...

  9. PL/SQL中查询Oracle大数(17位以上)时显示科学计数法的解决方法

    PL/SQL中查询Oracle大数(17位以上)时显示科学计数法的解决方法 参考文章: (1)PL/SQL中查询Oracle大数(17位以上)时显示科学计数法的解决方法 (2)https://www. ...

最新文章

  1. BGP水平分割的疑惑
  2. 双向slider滑动微信小程序组件slider组件
  3. Redis学习笔记(4)-List
  4. 浙江理工大学2019年4月赛
  5. 关于Oracle AUTONOMOUS TRANSACTION(自治事务)的介绍
  6. Nginx的官方简介
  7. 如何在Eclipse中添加Servlet-api.jar的方法
  8. Python中的偏函数和函数柯里化
  9. 有信宣布推出首款语音直播平台:红豆Live
  10. 电脑插上U盘双击打不开应用程序右键可以打开问题
  11. 深圳最牛街道办:腾讯华为设总部,百家上市公司年营收超2万亿
  12. 【转】用 Go 构建一个区块链
  13. 【已解决】Activity MainActivity has leaked window PhoneWindow$DecorView@ that was originally added here
  14. c语言最长递增子序列nlogn,最长递增子序列
  15. 特价机票退票费高达80% 律师称航班延误应补偿-特价机票-退票费-霸王条款
  16. 自己封装的数据库DbUtils的万能模板
  17. 设计1-腾讯设计导航
  18. jupyter notebook 中运行from scipy import stats之后报错FutureWarning:
  19. python-docx 中文个人翻译
  20. google map 地理编码API的两种方式

热门文章

  1. SpeedPan百度云不限速下载
  2. 2017年中兴算法大赛 迪杰特斯拉派
  3. EXCEL转PDF最便捷的方法
  4. 电脑打印机老是文档挂起无法打印怎么办
  5. 一个程序员的减肥方法(男女通用); 饮食+运动
  6. 轻量级的双向绑定工具 —— ukulelejs
  7. 三顾茅庐,七面阿里,25k*16offer,还原我的大厂面经
  8. ssd linux 硬盘备份,SSD最佳备份良伴 群晖3步搞定系统备份
  9. [译] 超快速的分析器(一):优化扫描器
  10. 机器学习 - Python Matplotlib 练习, 常见功能查阅