在《秒级展现的百万级大清单报表怎么做》中,我们介绍了无论 RDB 还是非 RDB,润乾报表都能够通过异步线程实现秒级海量大清单报表(以下简称:大报表)。实际业务中,除了查询明细,有时还要展现全量汇总数据,或者查询分组明细和计算分组汇总。本文就将介绍这些带有汇总和分组的大报表的开发方法。
带汇总的大报表
在大报表中计算汇总值与常规报表基于报表内数据进行的汇总不同,大报表由于采用异步线程,因此无法通过报表内数据进行汇总(因为每次只能取到部分数据),只能在数据处理阶段计算汇总值并交给报表呈现。
我们还是使用《秒级展现的百万级大清单报表怎么做》中 SQL 源大报表的例子,现在需要在每页最后一行显示订单汇总情况(订单数量、订单总额、运费总额)。
制作报表模板
增加一个数据集 ds2 计算汇总值:
报表中最后一行引入 ds2 汇总值进行呈现,这里为了每页都包含汇总数据,设置最后一行属性为报表尾。
当然,带有汇总的大报表性能跟计算汇总值的 SQL 执行效率强相关,所以可能会发现带有汇总的大报表要比单纯查询明细慢一些。
分组大报表
实际业务中,简单呈现大报表清单往往还不够,有时还需要对海量数据进行分组,呈现汇总及明细情况。下面,我们就以订单数据为例,查询一下按地区分组的明细及订单金额汇总。
需求分析
首先,需要呈现分组明细的报表无法在数据源端进行聚合,例如通过 SQL 聚合后就不包含分组明细了,除非再查询一次进行拼接,但查询两次显然会严重影响性能。因此我们需要尽量将明细数据读出后在应用端进行分组聚合。
那问题就来了,由于海量数据要通过分批读取的方法进行呈现,如何保证读取的每批分组数据是完整的?如果分组数据不完整,分组聚合的结果显然也就不正确了。因此,除了要保证能在应用端实现聚合,还要保证批量读取数据时分组数据的完整性。
下面就是润乾报表结合集算器(数据集)实现分组大报表的过程。
报表数据准备
我们需要编写集算器 SPL 脚本进行数据准备。这里,我们要编写两个 SPL 脚本,分别实现:查询分组明细并计算汇总,以及通过游标分批为报表返回结果集。
SPL 脚本 1:group-detail.dfx
设置脚本参数:
编写 SPL 脚本:
A | B | C | |
1 | =connect("db") | ||
2 | =A1.cursor@x(“select 货主地区, 订单 ID, 客户 ID, 订购日期, 运货费, 订单金额 from 订单 where 订购日期 >=? and 订购日期 <=? order by 货主地区”,begin,end) | /建立数据库游标查询订单数据,并按地区排序 | |
3 | for A2; 货主地区 | /按地区分组读取数据 | |
4 | =A3.derive(0:flag) | /增加标志位 | |
5 | =B4.sum(订单金额) | /分组汇总 | |
6 | >B4.insert(0,null,null,null,null,"订单金额小计:",B5,1) | /汇总值添加到明细数据中 | |
7 | return B4 | /返回明细及分组结果 |
SPL 解析:
1、 A2 查询数据库,SQL 中指定按照分组字段(货主地区)排序,以便后续可以每次取出整个分组进行汇总。为此需要在排序字段上建立索引,避免全表排序时间过长。
2、 A3 循环游标,以货主地区为标记,保证每次读取的记录数为一个地区数据
3、 B4 增加标志位,用于后续报表展现中突出汇总行。标志为 0 时表示是明细记录
4、 B5 针对每个分组进行汇总
5、 B6 将分组值追加到明细记录中,标志设为 1,表示是汇总记录
6、 B7 返回分组明细和汇总集合。注意这里 return 写在循环内,因此会多次返回分组集合
关于 for cs,n;x 用法在集算器 SPL 脚本中,for cs,n;x 表示针对游标 cs 通过循环遍历数据,每轮从游标读取 n 条记录或者直到记录中的 x 发生变化,循环全部结束后关闭游标。大数据量的分组取数是这种循环的常用之处。如果省略了 n 和 x,那就简单地返回游标中所有数据并关闭游标。函数的具体说明可以参考:http://doc.raqsoft.com.cn/esproc/func/forcsnx.html
当大数据集按照分组字段有序时,这种取数方式每次可以读取一个完整分组到内存中参与计算,不过这时仍然要求分组不能很大(内存能装下)。而在金融和电信行业中,经常要基于单用户做数据分析,也就是按用户分组,而每个用户的流水记录规模又较大,常规的拆分方法作起来十分复杂。而通过集算器就可以很好地解决这类问题了。
SPL 脚本 2:main.dfx
设置脚本参数:
编写 SPL 脚本:
A | B | |
1 | =cursor("group-detail.dfx",begin,end) | /调用 dfx 生成游标 |
2 | return A1 | /为报表返回游标 |
前一个 SPL 脚本 group-detail.dfx 解决了分组查询明细并汇总计算的问题。这个脚本 main.dfx 则可以分批次取数并提供给润乾报表,从而实现异步大报表呈现。脚本中 A1 通过 cursor 函数直接调用前一脚本生成游标,由 A2 将游标返回给报表。
关于 cursor() 函数使用 cursor 函数调用 SPL 脚本生成游标时,被调用的 SPL 脚本可以有多个返回结果集(例如在 for 循环中的多个 return),而游标取数(fetch)时可以依次使用多个 return 结果,无需等待所有结果集都准备好再使用,原理如下图所示。函数的具体说明可以参考:http://doc.raqsoft.com.cn/esproc/func/cursordfx.html
结合大报表使用 cursor() 函数运行原理
设计报表模板
设置报表参数,查询起止日期:
设置数据集引用 main.dfx 并传递日期参数:
按照大报表模板设计思路,编写表达式
为了将汇总行高亮显示出来,这里利用了数据准备阶段增加的标志位 flag 列,当 flag 值为 1 的时候代表该行为汇总行,设置背景色表达式:if(A3==1,-3355444)
设置大数据集
发布到 WEB
将做好的模板发布到 WEB 端,效果如下:
当然,标志位 flag 列也可以设置为隐藏。
效果调整
调整报表模板,将标志位 flag 列隐藏,并设置 B3 格的扩展属性 同值合并为“纵向合并”
展现时分组列则带有合并格的效果:
注意事项
在《秒级展现的百万级大清单报表怎么做》中,我们提示了大报表不要全表排序,也不适合高并发场景。除此以外,对于带有分组汇总和明细的大报表还应该注意:
单个分组不宜过大
由于计算分组明细和汇总值时需要将某一个分组数据全部加载到内存中进行计算,因此分组相对内存容量不宜过大,从而确保单个分组数据能进行全内存计算。