oracle求累积收益率,解决报表sql中的累计收益率问题?换个姿势,再来一次~
原标题:解决报表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中的累计收益率问题?换个姿势,再来一次~相关推荐
- Oracle数据库第四课——PL/SQL中的条件控制
知识点: PL/SQL 有 3 种类型的条件控制结构:IF.ELSIF 和 CASE 语句.掌握 IF 语句的用法, 掌握 ELSIF 语句的用法, 理解嵌套 IF 语句的用法, 掌握 CASE 语句 ...
- c# 经验谈:巧用Expression表达式 解决类似于sql中 select in 的查询(适合于中小型项目)...
我们在项目经常会碰到一些特殊需求 例如下拉框是复选的,查询条件是根据下拉框中复选项进行拼接 看到此图后大家肯定会说,这很简单嘛 将所有的选项 拼成"'1-3','5-9'" 然后 ...
- oracle 偶数与奇数,在PL / SQL中计算数字中的奇数和偶数
我们给定一个正整数数字,任务是使用PL / SQL计算数字中奇数和偶数的计数. PL / SQL是SQL与编程语言的过程功能的组合.它是由Oracle Corporation在90年代初开发的,目的是 ...
- oracle date 隐式转换,PL/SQL中的数据类型隐式转换规则
1) During INSERT and UPDATE operations, Oracle converts the value to the datatype of the affected co ...
- oracle 求一年多少天,SQL 计算一年有多少天
SQL 计算一年有多少天,计算当前年份有多少天. SQL 计算一年有多少天 问题描述 计算当前年份有多少天. SQL 计算一年有多少天 解决方案 计算当前年份有多少天,等同于计算下一年的第一天和当前年 ...
- Oracle ——如何确定性能差的 SQL
http://www.toadworld.com/KNOWLEDGE/KnowledgeXpertforOracle/tabid/648/TopicID/TSQ7/Default.aspx 本文主要说 ...
- 获取股票数据【实时更新股票数据、创建你的股票数据】、计算交易指标【买入、卖出信号、计算持仓收益、计算累计收益率】
在上一次获取股票数据[使用JQData查询行情数据.财务指标.估值指标]学习了使用JQData来查询股票相关数据, 这次则开始一点点构建咱们的量化交易系统了. 量化交易平台功能模块了解: 对于一个量化 ...
- oracle数据源的报表sql计算慢解决
http://blog.csdn.net/u012388497/article/details/17217705 问题描述 项目里有些报表出来的速度特别慢,尽管对润乾报表和Oracle数据库做了很多优 ...
- PL/SQL中查询Oracle大数(17位以上)时显示科学计数法的解决方法
PL/SQL中查询Oracle大数(17位以上)时显示科学计数法的解决方法 参考文章: (1)PL/SQL中查询Oracle大数(17位以上)时显示科学计数法的解决方法 (2)https://www. ...
最新文章
- BGP水平分割的疑惑
- 双向slider滑动微信小程序组件slider组件
- Redis学习笔记(4)-List
- 浙江理工大学2019年4月赛
- 关于Oracle AUTONOMOUS TRANSACTION(自治事务)的介绍
- Nginx的官方简介
- 如何在Eclipse中添加Servlet-api.jar的方法
- Python中的偏函数和函数柯里化
- 有信宣布推出首款语音直播平台:红豆Live
- 电脑插上U盘双击打不开应用程序右键可以打开问题
- 深圳最牛街道办:腾讯华为设总部,百家上市公司年营收超2万亿
- 【转】用 Go 构建一个区块链
- 【已解决】Activity MainActivity has leaked window PhoneWindow$DecorView@ that was originally added here
- c语言最长递增子序列nlogn,最长递增子序列
- 特价机票退票费高达80% 律师称航班延误应补偿-特价机票-退票费-霸王条款
- 自己封装的数据库DbUtils的万能模板
- 设计1-腾讯设计导航
- jupyter notebook 中运行from scipy import stats之后报错FutureWarning:
- python-docx 中文个人翻译
- google map 地理编码API的两种方式