多层科目任意组合汇总报表的性能优化 (下)

简介:

2.4 有序计算方案
在充分利用遍历一次的特点进行优化后,可能我们还会觉得计算性能有点慢,希望有进一步优化的空间。由于每次只需要取出总数据量的很小一部分 (100 个指标涉及的所有科目号大概几百个,即在几百万记录中取几百条),这时我们通常能想到的是:如果能利用数据有序直接进行有序查找(若源数据有序,可以快速定位到这几百条记录,不需要遍历几百万记录甚至更多的数据),将能够获得更好的查询效率。
我们可以利用集算器提供的 iselect() 函数对每个计算的指标进行有序查找,从而减少遍历次数。这里需要注意两个关键点:
1、 iselect()函数用单个主键的查找速度会比用多个主键查找更快,并且写法上也会简单很多。
2、 在数据预处理时,遇到多个主键时应该想办法合并成一个,并且数字化后进行排序,以便使用 iselect() 函数。
关于 iselect() 函数的具体用法和有序计算的解释这里不再赘述,可参考集算器教程的相关章节。
2.4.1 合并主键、排序
为了满足上面提出的两个关键点,我们需要对源数据重新预处理一遍,关于分组计算汇总值、利用跨行组计算累计值等原理上面已经讲过了,这里主要说合并主键和排序。
第一步,在原始数据中,用“年”和“月”两列字段动态计算一个变量值,称为“月号”,以便与“科目”字段合并成唯一主键。代码中相应的改动如下:
1
其他格子的代码,前面已经解释过了,这里不再赘述。
B3:首先在集算器中定义参数名称:inityear,设置值为 2014,如下图:
2
假设原始数据是从 2014 年开始的,所以把初始年份的默认值设置为 2014。所谓“月号”就是每条记录的时间是从初始年份 1 月开始的第几个月。比如:当前一条数据记录中年是 2017,月是 3 的话,那么根据这个公式的结果:月号 =(2017-2014)*12+3,也就是 2014 年 1 月开始的第 39 个月。将计算结果利用 run() 函数重新赋值给月字段,以便后面与科目构造唯一主键。
A4:按科目、月号进行分组,金额进行求和(前面已经解释过)
B5:对金额字段进行累计(前面已经解释过)
B6:计算后的结果集以追加的方式保存到集文件中(前面已经解释过),即总账凭证 -mid.btx,执行结果如下图:
3
第二步,对科目前 N 位分别汇总金额;如何计算多层科目汇总值前面已经讲过了,这里主要关注月号和科目合并成主键 key,然后进行排序。月号计算出来是 2 位(假设数据记录跨度不超过 99 个月),科目为固定的 10 位,这样为了保证合并成主键后的唯一性,需要定义新主键的总长度为 12 位。
这样,新主键的构造规则就是:key(12 位)= 月号 (月号为 2 位)10000000000+ 总账科目 (最长为 10 位)。有一个技巧需要说明一下:这里设定 key 的长度为 12 位,可以存放在一个 long 类型中,如果更长 (与需求有关),就要用字符串了,虽然会相对慢一点,但也影响不大。
集算器的 SPL 脚本如下:
4
A1-A3:前面已经解释过了,这里不再赘述。
A4:创建管道,将游标 A3 中的数据推送到管道,其中 ch.groupx() 函数针对管道中的有序记录分组并返回管道;按科目截取前 8 位、月号进行分组,累计金额进行汇总。返回的数据结构如下图:
5
A5:同理于 A4 返回管道,按科目截取前 6 位、月号进行分组,累计金额进行汇总。返回的数据结构如下图:
6
A6:返回游标,按科目截取前 4 位、月号进行分组,累计金额进行汇总。返回的数据结构如下图:
7

A7:多个游标运算结果合并成一个结果集;其中 ch.result() 代表管道的运算结果
A8:对 A7 的每条记录生成新序表,序表包含两个字段:由月号 (月号为 2 位)10000000000,然后再加上截取后生成的新的科目 (最长为 10 位),重新定义为 key 和累计金额汇总两列字段,接着再对 key 进行排序。
特别说明一下,cs.groupx() 函数按照字段分组后,会对该字段进行排序,也就是说运算后的结果本身就是有序的,所以我们可以利用这个特性,先按月号分组 (写前面),再用 cs.mergex() 函数按照月号、科目做有序归并运算,归并后的结果就不再需要排序了。相应地代码改动如下:
8
A9:计算后的结果集导出并保存到集文件中,即总账凭证 -later.btx。数据结构如下图:
9
4.2 有序查询
这样,我们就按照要求完成了数据预处理工作,接下来分两步验证报表查询:
1、定义子程序: 任意给定一个计算指标,能够快速返回指标汇总值;然后多次调用子程序来完成 100 个指标的计算,返回结果集。
2、任意给定 100 个计算指标,快速返回与之对应的指标汇总值。
2.4.2.1 多次 ISELECT 查询
首先,定义一个子程序,任意给定一个计算指标(可能只有科目号前面 N 位,比如前 4 位 /6 位 /8 位 /10 位等,自由组合出现),返回这个指标汇总值。
然后,通过调用子程序来完成 100 个指标的计算,先定义查询参数, yyyy 代表查询年,mm 代表查询月,比如:查询 2017 年 1 月的数据,如下图:
10
用子程序的样例:
11
A1:定义变量 inityear,假定原始数据是从 2014 年开始的,所以设置默认值为 2014;

B1:按照前面相同的规则生成“月号”。如果参数是 2017 年 1 月,执行结果如下:
12
C3:接着对对指标参数集合 B3 排序。执行结果如下图:
13
B4:根据指标 C3 中的参数集合与结果集文件中有序的 key 字段进行比对查找,返回游标;其中 @b 代表从集文件中读取。

B5:从游标中获取记录,执行结果如下图:
15
B6:对累计金额汇总求和,返回指标的计算结果。

C7:指标 A 的参数条件 (按科目号前 4 位截取的多个值形成的集合)

C8:指标 B 的参数条件 (按科目号前 4 位 / 前 6 位 / 前 8 位截取的多个值,形成的参数集合) ,剩余的 98 个指标,计算的写法类似 A8,参数的写法类似 C8,依次类推到 100。

A7:调用 func 子程序,把 C7 的指标参数值传入到子程序中,子程序计算后返回结果。

A8:同理,计算指标 B 的结果集

A107:合并 A7-A106 每个格子的值 (从上往下,100 个指标的计算结果),返回一个单列数据集,可以供报表工具使用。

这样做已经可以利用有序查询了,但计算 100 个指标还需要执行 100 次子程序的 iselect() 函数,依然遍历太多,编码过程也比较繁琐。

2.4.2.2 一次 ISELECT 查询

那么,有没有办法只做一次 iselect 查询呢?

答案是有!我们可以把 100 个需要计算的指标的科目号都整理好,然后执行一次 iselect() 函数,把所有指标汇总结果都查找出来,这样,就大功告成了。

这里,需要注意两个关键点:

1、需要将多个计算指标中的不同科目号进行合并、构造主键、排序。

2、利用 pos()的函数技巧,根据每个计算指标中多个科目号与月号构造的主键在结果集中的找到坐标位置 ( 与 key 列字段比对),返回位置序号, 接着根据位置序号在结果集中找到的累计金额汇总字段进行求和,求和结果再按位置序号倒回到每个指标中,即每个指标的汇总值计算完成。

为了便于理解,举个例子,详细解释一下利用 pos() 函数是如何做到定位计算的?示意图如下:
16
解释:指标 A 和指标 B 的所有科目号合并,然后统一排序生成序号,通过序号在有序结果集中找到对应的金额,再利用位置序号把金额倒回到每个指标中,每个指标下对多个科目号的金额汇总,即指标汇总值。

最终,计算 100 个指标的集算器的 SPL 脚本样例如下:
17
A22:把 A2 到 E21 范围内 100 个计算指标的科目号合并起来,其中 A2 格子代表指标 1,B2 代表指标 2,依次类推;合并完后,执行结果如下图:
18
B22:对 A22 指标中每一个科目号,分别利用月号 (月号为 2 位)*10000000000,加上当前科目号,形成指标的参数组集合。执行结果如下图:
19
C22:对指标参数组集合进行合并,然后排序
20
A23:打开预处理后的集文件对象

A24:根据指标 C22 中构造的参数组集合与文件中有序的 key 字段进行比对,记录返回成游标

B24:从游标中获取记录,返回所有科目号的查询到的结果集,执行结果如下图:
21
C24-D24:分别获取结果集中的:key、累计金额汇总。

A25:按照 A22 中每个科目号的顺序,在 B24 结果集中利用 key 列与当前科目号 + 月号构造的主键进行比对,然后返回位置序号,其中 pos() 函数中 @b 代表使用二分法查找,效率更高,但要求被寻找序列是有序的。运算结果如下图:
22
A26:利用 A25 每个成员坐标位置的序号,在结果集 B24 中对比找到的累计金额汇总字段进行求和,求和结果再按位置序号倒回到每个指标中,比如序号 1 返回的结果代表 A2 的参数查询出来的指标 1 汇总结果,序号 2 返回的结果代表 B2 的参数查询出来的指标 2 汇总结果。依次类推。即每个指标的汇总值计算完成。执行结果如下图:
23
A27:返回结果集,供报表工具使用。

至此,利用一次遍历,搞定所有事情,难题迎刃而解!

实测结果:报表从取数到展现整个环节大概需要1-2秒,其中指标计算部分用时不到1秒。

2.5 作为报表数据源
对于报表的制作过程来说,并不需要做什么改变,只需要把数据源切换到集算器即可。假定报表工具是润乾报表 V5:

首先,导入 excel 表样,创建数据集类型为集算器,选取已经做好的 dfx 脚本;同时设置相应的查询参数等。

然后,在报表的每个单元格里分别按顺序取值,即可得到每个指标汇总结果;比如单元格 C5 的表达式写法:=ds1.select(#1)(1),单元格 C6 的写法:=ds1.select(#1)(2),……,报表单元格表达式从上往下,依次类推。样例如下图:

作者:Andy
链接:http://c.raqsoft.com.cn/article/1535947863062
来源:乾学院
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
24
2.6 总结
在实际的报表开发过程中,当我们遇到问题,往往并不能一开始就想到最优的解决办法。我们可以试着先用最简单、最容易的办法实现,然后再一步步进行优化;对比每种方案的存在的缺陷及改进后所带来的性能提升,从而最终满足业务需求。

本文中我们就采用了这种方式,逐步优化的步骤如下:

1、多次遍历方案

2、一次遍历方案

3、预先汇总方案,查询部分在 2 的基础上进行优化

4、有序计算方案

整个过程中,我们用到的集算器相关技术包括:游标、管道、遍历复用、数据外置、分组子集、跨行组计算、有序计算 / 查询、二分法查找、位置序号等。

了解了这些概念并熟练掌握集算器相关的函数后,我们就可以写出高效的代码,快速实现报表数据集的准备工作!

三 引入集算器后的报表系统结构
实际业务中,我们针对客户提供的生产环境数据 (原数据表大概 6000 万明细记录),利用这种方案进行了 POC 实测。结果表明,原来需要 30-40 秒才能呈现的资产负债表,现在提高到 1-2 秒内。而且,集算器脚本可以与报表模板一起管理,从而有效降低应用管理的复杂度。

使用集算器进行预处理计算后,形成的数据缓存文件,能够很好的优化现有报表实现模式,有效解决大数据集报表运算慢的难题。

原有模式和引入集算器后的报表系统结构对比如下图所示:
25
根据报表的业务特点,通常具体的实现步骤如下:

1、集算器抽取来自数据源的数据,根据报表的业务规则,取出需要的维度、过滤字段、计算指标等明细记录。

2、集算器对明细记录进行预处理计算,生成报表需要的各类指标。

3、计算后的指标以数据缓存文件的方式存放,可以按照业务种类、模块关系、时间顺序进行多级目录管理,也可以和报表模板一起管理。

4、报表工具通过 JDBC 方式调用集算器,计算结果返回给报表工具呈现。

5、可以设置计划任务定时执行,完成上述各项数据预处理动作。

作者:Andy
链接:http://c.raqsoft.com.cn/article/1535947863062
来源:乾学院
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

相关文章
|
编解码 数据可视化 Java
3D模型拆分与合并展示,IVX真的可以简单实现
iVX 平台的优势和特点,包括逻辑完备性、操作流畅性、面向对象设计方法、可独立作为编程语言等方面的优势,下面来详细的介绍介绍。
129 0
|
4月前
|
存储 开发框架 前端开发
EAV模型(实体-属性-值)的设计和低代码的处理方案(2)--数据的查询处理
EAV模型(实体-属性-值)的设计和低代码的处理方案(2)--数据的查询处理
|
7月前
|
分布式计算 并行计算 算法
图计算中的性能优化有哪些方法?请举例说明。
图计算中的性能优化有哪些方法?请举例说明。
65 0
|
7月前
|
人工智能 算法 测试技术
【简历优化平台-03】轻字段信息的合理性及单独算法
【简历优化平台-03】轻字段信息的合理性及单独算法
【SQL开发实战技巧】系列(二十五):数仓报表场景☞结果集中的重复数据只显示一次以及计算部门薪资差异高效的写法以及如何对数据进行快速分组
本篇文章讲解的主要内容是:***如何使用lag函数让结果集重复数据只显示一次、用行转列pivot写法优化部门之间计算工资差异类似需求、如何通过ceil函数对已有数据进行分组打印、放假安排团队分组值班,如何通过ntile()over(order by )快速进行人员分组***
【SQL开发实战技巧】系列(二十五):数仓报表场景☞结果集中的重复数据只显示一次以及计算部门薪资差异高效的写法以及如何对数据进行快速分组
|
SQL 移动开发 BI
【SQL开发实战技巧】系列(二十三):数仓报表场景☞ 如何对数据排列组合去重以及通过如何找到包含最大值和最小值的记录这个问题再次用执行计划给你证明分析函数性能不一定高
怎样对数据组合重新排列并去重的问题、通过如何找到包含最大值和最小值的记录这个问题再次用执行计划给你证明分析函数性能不一定高【SQL开发实战技巧】这一系列博主当作复习旧知识来进行写作,毕竟SQL开发在数据分析场景非常重要且基础,面试也会经常问SQL开发和调优经验,相信当我写完这一系列文章,也能再有所收获,未来面对SQL面试也能游刃有余~。本篇文章主要介绍的两个方面,第一个方面曾经有好几个网友和同事问我,第二个问题真的是很多同行的通病,认为分析函数是万金油,一股脑用。
【SQL开发实战技巧】系列(二十三):数仓报表场景☞ 如何对数据排列组合去重以及通过如何找到包含最大值和最小值的记录这个问题再次用执行计划给你证明分析函数性能不一定高
|
关系型数据库 PostgreSQL RDS
多字段,任意组合条件查询(0建模) - 毫秒级实时圈人 实践
标签 PostgreSQL , 数组 , GIN索引 , 任意字段组合查询 , 圈人 , ToB分析型业务 , 建模 背景 你也许在一家ToB的数据分析公司,你可能设计了一张表(包括用户标识,及若干已经统计好的的属性值),你也许收集了一些用户的数据,你也许要为客户提供报表,你也许需要为客户提供任意属性值的组合查询,并快速的返回结果给用户。
9169 0
|
数据处理 Windows
数据处理第一节:选取列的基本到高级方法
博客原文:https://suzan.rbind.io/2018/01/dplyr-tutorial-1/ 作者:Suzan Baert 注意:所有代码都将作为管道的一部分呈现,即使它们中的任何一个都不是完整的管道。
1016 0
|
数据挖掘 Serverless
差异分析②
样品的无监督聚类 检查基因表达分析最重要的探索性策略之一是多维定标(MDS)图或类似的图。该图以无监督的方式显示了样本之间的相似性和不相似性,以便人们可以了解在进行正式测试之前可以检测差异表达的程度。
1159 0