报表项目中,大部分报表经过简单的设计、制作即可完成。但是,总有一部分复杂报表需要自定义数据集才能实现。自定义数据集是指报表的数据源不能通过简单SQL实现,需要用报表工具提供的API,调用项目组人员开发的程序来实现。这部分报表数量不多,但是编程、调试工作量较大,在整个项目中占用的时间反而更长。

那么为什么自定义数据集会成为报表项目的常态,会在每个项目中出现呢?如果是一定会出现,有没有更加高效的开发工具,让自定义数据集更容易实现呢?


我们知道,报表是由两部分组成的:数据计算和报表呈现。自定义报表出现的原因,是因为数据库中的原始数据结构与报表中要展现的数据结果之间差异较大,造成报表数据计算过程比较复杂。

有些报表连接的原始数据库是生产数据库,数据结构不适合直接展现,所以要写复杂的程序,才能将数据计算成报表需要的结果。如果报表连接的是经过整理的数据仓库,其数据结构也不一定能适合所有的报表,特别是项目后期出现的报表,一般都要在数据仓库的基础上做进一步复杂计算才能在报表中展现出来。


出现自定义数据源的另外一个原因,是报表工具需要连接多个数据库或者其他种类的异构数据源。比如:多个oracleoracledb2、数据库和文件等等。这个时候也需要写自定义数据源程序来取数据并且计算。

总之,编写自定义数据源程序处理复杂数据计算的好处是:无论数据源计算多么复杂,数据来自多少个不同的数据库或者文件,只要会写代码、肯写代码,一定能把所有数据集中到一起完成计算。但是,现有的编程手段,对于自定义数据源程序来说,并不是特别理想。


这里我们以使用最广泛的编程语言Java为例,来看一下自定义数据源程序的编写难度和编写效率。

Java是一种很强大的编程语言,程序员对Java的熟悉程度也较高。但是,我们在实际项目中会发现,使用Java来实现自定义数据源的工作量较大,开发难度较高。这是因为:Java并没有提供实现数据计算的常用类库,程序员需要耗费大量时间和精力去手工实现细节。例如:聚合,过滤,分组,排序,排名等。

再比如数据存储和访问的细节:每条数据,每个二维表都需要程序员用List/map等对象组合起来,再用嵌套的多层循环来计算。这类计算大多会涉及到批量数据之间的集合运算和关系运算,或者对象之间和对象属性之间的相对位置的运算。程序员实现这些底层计算逻辑非常费力。还有复杂的有序计算,Java处理起来工作量就更大了。


我们可以通过下面的例子来看一下java的自定义数据源编程:

某网络平台需要监测查看一定周期内的用户状况,需要为运营部门出具日报、周报、月报、年报等报表,每类报表中均包含本期与上期、上上期数据比较,故涉及数据量较大。这里以其日报为例(月报年报只是统计周期不同),报表格式如下:


需要说明的是:报表分为两部分,上半部分为用户明细数据,由于用户较多报表中只显示按本期在线时长排序后的前十名和后十名;下半部分为本期数据与上期、上上期的比较结果。

以润乾报表为例,使用Java来计算自定义数据源的主要代码如下:

获取报表参数

        Mapmap = ctx.getParamMap(false);

        if (map != null) {

            Iteratorit = map.keySet().iterator();

            while (it.hasNext()) {

                // 分别取得参数

                Stringkey = it.next().toString();

                data_date= map.get(key).toString();

            }

        }

 

执行数据库sql取数

            String sql ="selecta.userid auserid,a.first_logout_time,b.userid buserid,b.onlinetimebonlinetime,b.account baccount,"

                  +"c.useridcuserid,c.onlinetime conlinetime,c.account caccount,d.useridduserid,d.onlinetime donlinetime,d.account daccount,"

                  +"from"

                  +"(select   v.userid, v.first_logout_time"

                  +"from     t_dw_zx_valid_account v"

                  +"where    v.standard_7d_time is not null) a,"

                  +"(select   userid, sum(onlinetime) onlinetime,max(account)"

                  +"from    t_dw_zx_account_status_day"

                  +"where    logtime >= to_date('"+start_time_tm+"','yyyy-mm-ddhh:mi:ss')"

                  +"and      logtime <"+end_time_tm+"','yyyy-mm-ddhh:mi:ss')"

                  +"group byuserid"

                  +"havingmax(account) is not null) b,"

                  +"(select  userid, sum(onlinetime) onlinetime, max(account)"

                  +"from     t_dw_zx_account_status_day"

                  +"where    logtime >= to_date('"+start_time_lm+"','yyyy-mm-ddhh:mi:ss')"

                  +"and      logtime <  to_date('"+start_time_tm+"','yyyy-mm-dd hh:mi:ss')"

                  +"group byuserid"

                  +"havingmax(account) is not null) c,"

                  +"(select   userid, sum(onlinetime) onlinetime,max(account)"

                  +"from     t_dw_zx_account_status_day"

                  +"where    logtime >= to_date('"+start_time_lm_1+"','yyyy-mm-dd hh:mi:ss')"

                  +"and      logtime <  to_date('"+start_time_lm+"','yyyy-mm-ddhh:mi:ss')"

                  +"group byuserid"

                  +"havingmax(account) is not null) d"

                  +"where  a.userid = b.userid(+)"

                  +"and    a.userid = c.userid(+)"

                  +"and    a.userid = d.userid(+))"

                  +"order byb.onlinetime desc";

 

为了降低程序的复杂度,数据初步加工(分组、过滤、排序)仍然使用sql完成。

 

获取列名

            for(int i=0;i<colCount;i++){

                colName.add(rsmd.getColumnName(i+1));//列名

                type= rsmd.getColumnType(i+1);

            }

 

读取表数据,将其存入List

            while (rs.next()) {

                List<Object>rowData = newArrayList<Object>();

                for(int i=0;i<colCount;i++){

                    rowData.add(rs.getObject(i+1));

                    System.out.println("rowData"+i+"="+rowData.get(i));

                }

                data.add(rowData);

            }

 

构造数据集ds1

DataSet ds1 = new DataSet("ds1");

        for (int i = 0; i <colName.size(); i++) {

            ds1.addCol(colName.get(i));// 设置数据集的字段

        }

        Rowrr = null;

 

遍历List计算汇总值

        for(inti=0;i<data.size();i++){

            List<Object>row_data = data.get(i);

            boolean flag1=false;

            boolean flag2=false;

            boolean flag3=false;

            boolean flag4=false;

            boolean flag5=false;

           

            for(intj=0;j<row_data.size();j++){

                Objectsingle_data = row_data.get(j);

                Stringstr_single_data = single_data.toString();

                /****************计算汇总值************************/

                if(j==3 &&single_data!=null){//buserid is not null

                    flag1=true;

                }elseif(j==6 &&single_data!=null){//cuserid is not null

                    flag2=true;

                }elseif(j==9 &&single_data!=null){//duserid is not null

                    flag3=true;

                }elseif(j==2){

                    if(str_single_data.compareTo(start_time_lm)>=0&& str_single_data.compareTo(start_time_tm)<0){

                        flag4=true;//

                    }

                    if(str_single_data.compareTo(start_time_lm)<0){

                        flag5=true;

                    }

                }

               

                if (flag2&&flag3){

                    count1++;  

                }

                if(flag3&&!flag1){

                    count2++;  

                }

                if(flag4){

                    count3++;

                }

                if(flag1&&flag4){

                    count4++;

                }

                if(!flag3&&flag2&&flag5){

                    count5++;

                }

                if(!flag1&&!flag3&&flag2&&flag5){

                    count6++;

                }

            }

 

前十名数据

            if(i<=10){

                // 设置数据集中的数据

                rr= ds1.addRow();

                for (int j = 0; j<row_data.size(); j++) {

                    rr.setData(j+ 1, row_data.get(j));             }

            }

        }

 

后十名

for(inti=0;i<data.size();i++){

            List<Object>row_data = data.get(i);

                    if(i>data.size()-10){

                // 设置数据集中的数据

                rr= ds1.addRow();

                for (int j = 0; j<row_data.size(); j++) {

                    rr.setData(j+ 1, row_data.get(j));

                }

            }

        }

 

上面的代码已经很长了,如果再加上获取数据库连接、计算前n天和后n天日期后,代码就会更长。而且,上述代码中数据的分组排序操作是由数据库(sql)完成的,如果考虑用Java来实现,就要写比目前这些sql更多的代码。通过这个例子可以看出,Java在处理结构化数据时的代码量较大、编程难度较高。


那么有没有新的开发工具,可以减少代码量,降低开发难度呢?

集算器是一种创新的编程语言,提供了大量的数据计算类库。用集算器来实现自定义数据源编程,可以有效的降低开发难度,减少开发工作量、代码量。

同样是上边这个例子,用集算器esProc来实现的代码如下:

自定义数据源是报表开发的常态_java

A4-A6:进行数据过滤;

A7-A9:按userid分组;

A10:将以上结果集进行关联;

A11:基于A10进行过滤后按在线时长排序;

A12:新序表,用于读取前后十名记录;

A13-A14:通过序号分别取前后十名记录;

A15-A20:计算汇总值;

A22:将前十名、后十名记录以及汇总值分别以不同结果集通过集算器JDBC返回给报表。

 

从这个例子可以看出,集算器可以较好的解决Java存在的问题。

集算器脚本可以分步编写,按照自然思维实现业务逻辑代码,思路较为清晰,代码长度较短。集算器使得我们在一个屏幕内即可读完整个程序代码,代码的简洁是很明显的。另外,集算器对于有序运算的有效支持,使得取前后十名(A13A14)非常容易,不必像Java那样,必须写个循环才能完成。这样既能完成自定义数据源的复杂计算,又不会像使用Java代码那样编写较长难度较高的代码。

同时,报表工具可以通过Jdbc接口调用集算器编写的自定义数据源程序。与JavaAPI接口比较,Jdbc调用的配置更简单。同时,集算器代码是解释执行的,无需编译,程序的更新升级也更简单,只需要替换脚本程序文件即可。


值得一提的是,集算器的免费版本就可以很好的实现自定义数据源编程,如果项目组在报表开发过程中遇到类似上述的例子的情况,可以考虑引入集算器来实现。