润乾集算报表优化应用结构之减少存储过程

在报表应用中经常会使用存储过程实现报表的数据计算,但这会带来多方面的问题。存储过程的包只提供一层分类,无法用树形结构,容易造成代码管理混乱。有些程序员直接在现场在线修改存储过程,也不利于代码管理。升级存储过程的时候需要数据库的写权限,会对数据安全性造成影响。同时,由于SQL固有的一些问题(数据无序、缺乏集合、无法引用、分步不彻底)等,使得存储过程的编程比较困难。

很多情况下是为了提高性能而选择存储过程,但实际效果也不尽如人意。这主要是因为报表数据的计算一般都比较复杂,很难用SQL直接完成,需要通过循环遍历等代码来完成。这样的计算要比SQL慢一个数量级,有些语句的执行速度甚至还会低于外部的Java程序。

还有一个问题,存储过程是存储在数据库内的,而报表工具的模板一般是文件形式,两者要一起工作才能完成报表,但分开存储容易导致不一致,会增加管理难度。例如:某报表模板已经被删除了,但对应的存储过程未删除等;或者别人把某个存储过程改了而未通知报表开发者(因为数据库是所有开发者共享的)。

可以采用润乾集算报表及其内置的集算引擎来完成复杂的数据源计算,从而尽量避免使用存储过程。系统结构对比图如下:

集算报表的报表文件(.rpx)和集算脚本(.dfx)是独立的文件,可以用操作系统的树形目录管理,某些简单脚本还可以直接写进rpx,两者容易保持一致,方便管理。集算报表在应用程序中的升级时是通过替换这两种文件来完成的,可以避免在线修改运行环境。集算脚本解决了前述SQL的固有问题,更接近自然思维,编程比存储过程容易很多。

从性能角度,集算引擎提供了并行计算的能力,可以充分发挥服务器多CPU多核的作用,也可以连接集算服务器集群,实现多机并行,在许多情况可以获得超过存储过程的性能。

从上图也可以看到,某些情况下还是要用存储过程。这是因为涉及数据量大的库内运算用存储过程会更快。我们的目标是利用集算报表尽量减少存储过程的使用,使存储过程的数量减到最小。

下面,我们通过具体的例子,来看一下集算报表是如何避免存储过程的。

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

报表分为两部分,上半部分为用户明细数据(本期、上期、上上期在线时长均不为空),由于用户较多报表中只显示按本期在线时长排序后的前十名和后十名;下半部分为本期数据与上期、上上期的比较结果(允许本期、上期、上上期在线时长为空)。

数据来自于两个数据表:

create tableT_DW_ZX_ACCOUNT_STATUS_DAY

(

LOGTIME   DATE,--日志时间

USERID    NUMBER(12),--用户号

ACCOUNT   VARCHAR2(50),--账号

ONLINETIME NUMBER(8),--在线时间

PAY       NUMBER(11),

EXPEND    NUMBER(11),

TOP_LEVEL NUMBER(4)

);

create tableT_DW_ZX_VALID_ACCOUNT

(

USERID            NUMBER,--用户编号

FIRST_LOGOUT_TIME DATE,--第一次登出时间

STANDARD_7D_TIME  DATE,

STANDARD_14D_TIME DATE,

ACCOUNT           VARCHAR2(50)

);

首先,看一下存储过程的实现方式(为了说明方便,将其分成颜色不同的四部分):

CREATE OR REPLACE PACKAGE BODY CURSPKG AS
  PROCEDURE sp_query_user_status_day(data_dateIN varchar2,
                                    top10     OUT T_CURSOR,
                                    last10    in outt_cursor,
                                    var1      out number,
                                     var2      out number,
                                    var3      out number,
                                    var4      out number,
                                    var5      out number,
                                    var6      out number) IS
    V_CURSOR1 T_CURSOR; --top10
    V_CURSOR2 T_CURSOR; --last10
    V_CURSOR  T_CURSOR; --temp table
    v_ttime   date;
    temp_num  number;
   
    v_valid_user_conti_act1      number;         
    v_valid_user_back1           number;         
    v_valid_user_conti_act_lost1 number;          
    v_valid_user_active_lost1    number;         
    v_valid_user_add_lost1       number;          
    v_valid_user_back_lost1      number;         
 
  BEGIN
    v_ttime := to_date(data_date, ‘yyyy-mm-dd‘);
 
    --for temp table
    select count(1) into temp_num fromaccount_status_day_temp;
    if temp_num > 0 then
      delete fromaccount_status_day_temp;    --delete first
    end if;
 
    insert intoaccount_status_day_temp
      select *
        from (selectv.userid, v.first_logout_time
                fromt_dw_zx_valid_account v
               wherev.standard_7d_time is not null) a,
             (select userid, sum(onlinetime)onlinetime, max(account)                                                                                                                               
                fromt_dw_zx_account_status_day
               where logtime >=v_ttime
                 and logtime <v_ttime + 1
               group byuserid
              having max(account)is not null) b,
             (select userid, sum(onlinetime)onlinetime, max(account)                                                                                                                              
                fromt_dw_zx_account_status_day
               where logtime >=v_ttime - 1
                 and logtime <v_ttime
               group byuserid
              having max(account)is not null) c,
             (select userid, sum(onlinetime)onlinetime, max(account)                                                                                                                          
                fromt_dw_zx_account_status_day
               where logtime >=v_ttime - 1 - 1
                 and logtime <v_ttime - 1
               group byuserid
              having max(account)is not null) d
       where a.userid =b.userid(+)
         and a.userid =c.userid(+);
 
     commit;
    
     --top 10
    open V_CURSOR1 for
      select *
        from (select rownum,
                     a.auserid userid,
                     a.first_logout_time,
                     a.bonlinetimecurrent_onlinetime,
                     a.conlinetimelast_onlinetime,
                     a.donlinetimelast_last_onlinetime
                fromaccount_status_day_temp a
               order bybonlinetime desc)
       where rownum <11;
      
      --last 10
      open V_CURSOR2 for
      select *
        from (select rownum,
                     a.auserid userid,
                     a.first_logout_time,
                     a.bonlinetimecurrent_onlinetime,
                     a.conlinetimelast_onlinetime,
                     a.donlinetimelast_last_onlinetime
                from account_status_day_temp a
               order bybonlinetime asc)
       where rownum <11;
      
       top10 := V_CURSOR1;
       last10 := V_CURSOR2;
 
    --total
   select
            valid_user_conti_act       
          , valid_user_back            
          ,valid_user_conti_act_lost  
          , valid_user_active_lost     
          , valid_user_add_lost        
          , valid_user_back_lost      
    into
            v_valid_user_conti_act1
          , v_valid_user_back1
          , v_valid_user_conti_act_lost1
          , v_valid_user_active_lost1
          , v_valid_user_add_lost1
          , v_valid_user_back_lost1
   from
   (select  count(case when buserid isnot null and cuserid is not null then1 else null end) valid_user_conti_act 
           , count(case whencuserid is null and buserid is not nulland first_logout_time  <  v_ttime-1 then 1 else nullend)
           , count(case whencuserid is not null and buserid is nullthen 1 else null end)  valid_user_active_lost 
           , count(case whenduserid is not null and cuserid is notnull and buserid is null then 1 else nullend) valid_user_conti_act_lost   
           , count(case whenduserid is null and cuserid is not nulland first_logout_time < v_ttime-1 and buserid is nullthen 1 else null end) valid_user_back_lost   
           , count(case whenbuserid is null and first_logout_time  >= v_ttime-1 and  first_logout_time < v_ttime then 1 elsenull end) valid_user_add_lost
    from account_status_day_temp);
   
         var1 := v_valid_user_conti_act1;
         var2 := v_valid_user_back1;
         var3 :=v_valid_user_conti_act_lost1;
         var4 :=v_valid_user_active_lost1;
         var5 := v_valid_user_add_lost1;
         var6 := v_valid_user_back_lost1;
   
  END sp_query_user_status_day;
END CURSPKG;

该存储过程是为上述用户统计日报表服务,主要计算用户当期和历史时期的比较情况,其中包括明细数据前十名和后十名,用户新增与流失统计等。

第一(蓝色)部分:根据用户明细和状态表过滤汇总数据,按用户计算本期、上期、上上期情况统计;该中间结果存入临时表(避免重复计算),供后续计算使用。

第二(橙色)部分:根据第一部分的计算结果排序后,取前十名,结果以游标返回;

第三(绿色)部分:与前项类似,倒序排序取最后十名,结果以游标返回;

第四(紫色)部分:根据第一部分计算结果完成对各项综合统计指标计算,结果以六个输出参数返回。

该存储过程综合考虑了报表工具的计算能力不足的因素,将尽量多的计算都放到存储过程中完成,这点是值得肯定的。但其中使用了大量的复杂sql,以及多结果集的输出方式(游标)无疑增加了编程难度。

用集算报表来实现这个需求,首先要编写集算器脚本:

A1:连接提前配置好的oracle数据库。

A2-A6:从数据库按照条件和分组汇总、取数。其中的A3、A4、A5的group虽然也可以放到集算脚本中实现,但是用sql实现的好处是:简单运算尽量让数据库去做,可以让取出数据变少,节省JDBC的传输时间;复杂的过程性计算才放到集算脚本做,可以发挥数据库和集算脚本各自的优势。

A7:将以上结果集进行关联。

A8:新序表,用于读取前后十名记录。

A9-A10:通过序号分别取前后十名记录。

A11-A17:计算汇总值。

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

集算器脚本编写之后,保存为test.dfx,集算报表要新增集算器数据集来调用:

集算报表接收集算器脚本返回的三个结果集,其中”test.dfx”为集算器脚本名称。

下一步,要按照需求绘制报表模板文件,即可完成报表设计过程:

时间: 2024-10-26 23:32:20

润乾集算报表优化应用结构之减少存储过程的相关文章

润乾集算报表优化应用结构之报表数据源复用

在报表项目中,经常有多个报表的数据源计算方法有共同的部分.使用润乾集算报表,采用可挂接算法的方案时(可参考[润乾集算报表优化应用结构之可挂接算法]),可以更方便地将这些共同部分用同一个脚本来完成,从而实现算法复用.算法复用的好处是:一个算法只实现一次,不会出现同一个算法多处实现导致不一致的情况.同时也避免一个算法实现很多次的重复劳动,减轻工作量. 下面通过两个报表复用同一个算法的例子来看一下具体的实现方法.报表1是"员工绩效工资明细表",可以按照STATE来选择不同的员工例如:STAT

润乾集算报表优化应用结构之混合数据源

在报表项目中,报表源数据常常会来自于多种异构数据源.例如:关系型数据库(oracle.db2.mysql),nosql数据库(mongodb),http数据源,hadoop(hive.hdfs)甚至是excel或者文本文件.通常的做法是采用ETL工具,将这些数据源都同步到数据仓库中.但是这样做的问题在于:1.配置复杂,难度较大:2.成本较高:3.数据无法实时访问,需要有较长时间的延迟:4.数据仓库的建设和管理都比较复杂:5.如果数据量很大效率会很低,而且要不断的ETL去各个应用系统同步数据:6.

润乾集算报表优化应用结构之实现T+0实时报表

在报表项目中,客户越来越关注源数据的实时性,希望看到最新发生的数据在报表中体现出来.但是,传统的报表工具+数据仓库+ETL方式很难做到这一点,往往是只能看到昨天.上周甚至是上个月的情况,也就是T+1.T+7.T+30统称T+n报表.很难实现T+0报表,也就是能体现实时信息的报表. 分析其原因在于:1.如果报表的历史数据和最新数据都从客户的生产系统读取,虽然可以实现T+0报表,但是会对生产数据库造成压力,影响客户的业务.2.如果采用数据仓库的方式,那么ETL从生产库中取出数据,需要较长的"窗口时间

润乾集算报表优化应用结构之可挂接算法

在报表项目中,有些报表的数据计算方法会经常改变.例如:某企业员工的实际工资是通过绩效得分计算出的,算法经常变动,需要在不改动其他代码的情况下用新算法替换旧算法.如果用Java来实现计算的话,虽然可以实现动态可挂接计算模块,但是存在缺乏基础类库.占用多余内存等问题. 采用润乾集算报表可以很好的解决这些问题,实现低耦合.热部署的动态挂接算法.集算报表挂接算法系统结合和其他报表工具+java的系统结构对比图如下: 上图可以看出,java程序必须要编译.打包才能更新.集算脚本是解释执行的,脚本文件同时也

润乾集算报表优化应用结构之报表复杂数据源的管理

在报表项目中,常常有些复杂数据计算是为一个报表专用的,其它报表用不到.可以用SQL实现写进报表数据源中,但由于SQL无法分步计算,经常会写出非常复杂难懂的长语句,不利于调试和维护.如果用Java或者存储过程来实现,计算程序会和报表模板又会分开,不利于管理.使用润乾集算报表的脚本数据集来实现报表专用计算,既可以写出简单易懂的分步骤计算脚本,又可以将脚本存放在报表模板中利于管理.系统结构的对比如下图: 下面通过一个具体的报表例子来看一下集算报表脚本数据集的用法.<年度客户销售分析报表>可以选择年份

润乾集算报表优化应用结构之数据分库存储

报表项目中,可能会出现报表源数据来自于不同数据库的情况.这是因为同一张报表可能会从多个业务系统取数据.例如:员工信息从人力资源系统中取出,销售数据从销售系统中取出.还有一种可能是,同一应用系统的数据库负载太大,不得已分成多个数据库的情况.例如:销售系统数据分成当前库和历史库. 报表工具需要连接的可能是同样类型的数据库,比如都是oracle或者db2:也可能是不同类型的数据库. 报表应用中,数据分库存储的解决办法有:1.建设专门的数据仓库:2.利用跨库访问的技术. 专门数据仓库的建设和管理比较复杂

润乾集算报表优化应用结构之本地计算

在报表项目中,常常会碰到数据库压力很大影响整个系统性能的问题.由下面的传统方案的结构示意图可以看出,全部数据存储和源数据计算都放在数据库完成.当并发访问量较大的时候,虽然每个报表的数据量不大,还是会造成数据库压力过大,成为性能的瓶颈.多数数据库厂商提供的jdbc接口传输数据比较缓慢,在并发量较大的情况,对报表系统性能的影响也非常明显. 这种情况时可以考虑采用润乾集算报表提供的本地计算方案. 所谓本地计算,是将一部分计算任务从数据库中移出到报表服务器中完成.大多数有一定规模的应用系统中,数据库和应

润乾集算报表提升性能之过程优化

报表出现性能问题需要对数据源计算进行优化时,执行路径难以确定从而被干预是阻碍报表优化的难题之一.由于数据库执行路径对开发人员不透明,报表优化需要指定执行路径时,程序员会很难甚至无法干预.而一般报表工具不具备强计算能力,大部分计算仍然要依靠数据库进行,这就导致很多报表优化效果不理想. 不同于一般报表工具,润乾集算报表内置了专门用于数据计算的集算引擎,开发人员可以通过编写集算脚本完成报表数据源准备.与数据库执行SQL路径不可控相比,集算脚本的执行过程是可控的,开发人员可根据实际情况编写或更改计算执行

用润乾集算报表实现实时报表(T+0)的方案

在报表项目中,客户越来越关注源数据的实时性,希望看到最新发生的数据在报表中体现出来.但是,传统的报表工具+数据仓库+ETL方式很难做到这一点,往往是只能看到昨天.上周甚至是上个月的情况,也就是T+1.T+7.T+30统称T+n报表.很难实现T+0报表,也就是能体现实时信息的报表. 分析其原因在于:1.如果报表的历史数据和最新数据都从客户的生产系统读取,虽然可以实现T+0报表,但是会对生产数据库造成压力,影响客户的业务.2.如果采用数据仓库的方式,那么ETL从生产库中取出数据,需要较长的"窗口时间