使用Jasper或BIRT等报表工具时,常会碰到一些非常规的统计,用报表工具本身或SQL都难以处理,比如源数据不符合交叉表的要求,需要转置后再呈现。集算器具有结构化强计算引擎,集成简单,可以协助报表工具方便地实现此类需求。下面通过一个例子来说明转置交叉表的实现过程。
数据库表booking汇总着各年度商品的预定数据,有四个字段,包括年份和三种预定状态,部分数据如下:
报表要求呈现指定年份及上一年的预定情况,其中行组有三项,即三种预定状态,列组是年份,测度是当年的预定数据。此外要汇总出指定年份各预定状态的增长率。表样如下:
可以看到,这张报表的难点是:源数据无法直接用于交叉表,汇总列要用相对位置来动态计算。如果能将源数据转置,并将汇总列事先计算出来,则会显著降低难度,比如下面这样:
下面用集算器准备报表所需的数据,代码如下:
A1=yearBegin=yearEnd-1
yearEnd是来自报表的参数,表示用户指定的年份,比如2014。A1中的代码用来计算上一年(比如2013),为了方便引用,上一年定义为yearBegin。
A2=myDB1.query(“select * from booking where year between ? and ? order by year desc”,yearBegin,yearEnd)
这句代码用来从数据库查出指定年份及上一年的数据。myDB1是数据源名,指向MYSQL。函数query可执行SQL语句,也可以接受参数。假设yearEnd=2014,则A2的计算如下:
A3=create(row,col,value)
这句代码用来新建序表A3。A3有三个字段:row、col、value,将来可存储整理之后的数据。新建后的A3如下:
需要注意的是,序表类似数据库结果集,也是结构化二维表,但序表是泛型的,同一个字段可以存储不同的数据类型,序表也是有序的,可以按序号访问数据。利用序表的这些特点可以方便地实现本案例。
A4: for ["visits","bookings","successfulbookings"]
这句代码对集合["visits","bookings","successfulbookings"]进行循环访问,在循环中向A3追加数据,最终准备出报表需要的数据。for语句的作用范围是B4-C7,用自然的缩进就可以表示,无需括号或begin/end等标记。在作用范围里可以用A4来引用循环变量,即for语句所在单元格的格名。比如进行第一遍循环时,A4的值等于”visits”。
下面看循环体中的代码。
B4=endValue=eval(“A2(1).”+A4)
这句代码可从A2动态地取出第一条记录的预定状态数据。函数eval可将字符串解析为表达式,比如第一遍循环时,“A2(1).”+A4会被解析为A2(1).visits,计算结果是500。其中“A2(1)”表示第一条记录,“.visits”表示取出该记录的visits字段,即下图红框处:
C4=beginValue=eval(“A2(2).”+A4)
和endValue类似,beginValue从A2中动态地取出第二条记录的预定状态数据。第一遍循环时,endValue等于400。
B5=A3.insert(0,A4,A2(1).year,endValue)
C5=A3.insert(0,A4,A2(2).year,beginValue)
这两句代码向A3追加记录。函数insert可以向序表插入记录,第一个参数可以指定插入的位置,如果这个参数为0,则表示在最后追加记录。
比如第一遍循环时,B5追加的记录是:”visits”、2014、500,C5追加的记录是”visits”、2013,400。追加后A3如下:
B6=endValue/beginValue-1
这句代码计算指定年份的增长率,比如第一遍循环时,B6=500/400-1=0.25。
C6=if(B6>0:”+”,B6<0:”-”)+string(B6,”#%”)
这句代码用来格式化B6,算法是:如果B6大于0,则在百分数前面加“+”号,如果小于0,则加“-”号。比如第一遍循环时,C6=”+25%”。注意:格式化数据适合用报表实现,所以本步骤并非必须。
B7=A3.insert(0,A4,string(yearEnd)+”/”+string(yearBegin),C6)
这句代码向A3追加新记录,比如第一遍循环时,插入的记录是”visits”,”2014/2013”,”+25%”,如下图:
值得注意的是,这次插入的记录都是字符串,和之前的类型不同。
整个循环执行后,报表需要的数据就会全部追加在A3中,如下:
result A3
这句代码将A3返回给报表工具。集算器对外提供JDBC接口,报表工具会将集算器识别为普通数据库,集成方案请参考相关文档。
接下来以JasperReport为例设计一张简单的交叉表,模板如下:
报表中需要定义一个参数pyearEnd,用来对应集算器中的参数。预览后可以看到报表结果:
报表调用集算器的方法和调用存储过程一样,比如将本脚本保存为booking.dfx,则在JasperReport的SQL设计器中可以用booking $P{pendYear}来调用。