返回
 计算机世界2001年第14期

Oracle中Forms与Reports的集成

湖南大学信息管理系 方纪旋

  问题的提出

Oracle产品Developer 2000中包括多种开发工具,其中在具体工作时用得最多的是Forms和Reports。Forms是一个设计表单的工具,利用它可以灵活方便地定义各种表单对象,以简化用户在运行期对相关数据库的操作(查询、插入、删除和更新等);Reports则是一个设计报表的工具,利用它可以按照用户的要求方便地生成报表。

在Oracle应用系统中,大量的具有交互性的功能是由Forms实现的。利用Forms所生成的表单模块可以彼此调用,实现业务流程的控制并完成与数据库有关的复杂操作;而由Reports所设计的报表模块往往当做最底层的功能被表单模块所调用。对于一些Oracle用户来说,独立地用好Forms或Reports是没有问题的,但他们却很少考虑如何使二者有机地结合起来,利用Forms对数据操纵的灵活性,由Forms模块向Reports模块传送大量结构化或非结构化的实时数据,减少(或避免)Reports对数据库的访问,从而高速地生成报表并保证报表数据与表单数据的一致性。本文通过实例介绍一种利用Forms的封装例程RUN_PRODUCT( )在Forms模块中集成Reports模块的方法,利用该方法可以把Forms和Reports有机地结合起来,减少不必要的操作,提高工作效率。

RUN_PRODUCT例程

在Forms模块中调用Reports模块,最有效的方法是利用Forms的封装例程RUN_PRODUCT()。目前,很多程序开发人员在Forms模块中调用Reports模块时,或者没有采用这一例程,或者虽然采用这一例程但并没有充分利用参数表来传递表单中现成的数据,或者只是利用参数表来传递少量的文本参数(非结构化数据)作为Reports中的查询条件,报表的数据仍然要从数据库中查询得到,这样既增加了数据库服务器的负载,又增加了网络的流量,而且报表生成的速度慢,丧失了数据的实时性与一致性。因此,深入了解并熟练掌握参数表的使用方法便显得非常重要。

在使用RUN_PRODUCT( )之前要先建立参数表,并向参数表中添加参数。RUN_PRODUCT( )的使用格式如下:

RUN_PROCDUT(REPORTS, ‘report_name', SYNCHRONOUS, RUNTIME, FILESYSTEM, pl_id, NULL);

其中各参数含义如下:

REPORTS:说明被调模块是报表模块;

report_name:被调模块的完整路径名;

SYNCHRONOUS: 说明被调模块以同步的通信方式运行(被调模块退出之后主调模块才能继续执行,否则为ASYNCHRONOUS,即异步方式);

RUNTIME:说明被调模块的执行方式为前台方式(否则为BATCH,即后台方式);

FILESYSTEM:说明被调模块的存储位置是文件系统(否则为DATABASE,即数据库系统);

pl_id:用户定义的参数表标识符。

使用参数表的要领

由Forms模块向Reports模块传递的数据可分为两类:一类是非结构化数据,即报表表头(表尾)的数据;另一类是结构化数据,即报表表目的二维数据。在如图1所示的报表中,学年、学期、班级编号、班级名称、专业名称、课程编号、课程名称、学分、班级人数等,属于表头数据;而学号、姓名、性别、修读性质、平时成绩、考试成绩、总评成绩、任课教师等属于表目数据。

图1 Reports模块执行情况

参数表中的参数有两种,文本参数(TEXT_PARAMETER)和数据参数(DATA_PARAMETER)。文本参数是对字符型数据的引用,数据参数是对记录组的引用。对于非结构化数据,可借文本参数将数据加入参数表(注意:文本参数只接受字符型数据,对于其他类型的数据要通过TO_CHAR( )函数先转化为字符型数据);而对于结构化数据,则先要将数据存入记录组(RECORD_GROUP),然后借数据参数才能将数据加入到参数表中。注意:Forms模块的记录组中的各列和Reports模块中的查询(QUERY)对象的各列在名称、类型和顺序上要完全一致。这里文本参数是字符串指针,数据参数是记录组(结构数组)的指针,而参数表则是存储上述指针的指针数组。

如果RUN_PRODUCT( )的参数表中未加入与Reports模块中的查询对象同名的数据参数,则Reports 从数据库中查找与其模块中的查询对象相匹配的数据;如果RUN_PRODUCT( ) 的参数表中加入了与Reports模块中的查询对象同名的数据参数,则Reports 并不从数据库中查找数据,而是以参数表中的同名数据参数所引用的记录组作为其模块中的查询对象的数据源,在这种情况下,Reports查询对象的SELECT语句所映射的数据集合是不起作用的。

实现集成的具体方法

本文例子中用Forms模块来对学生的期末成绩进行管理,Forms模块运行如图2所示。

其中:

标以单位、班级、课程、成绩的各个块对象间存在级联的主从关系;

各个块对象中,有的项因不需要使用人员予以关注,故将其定义为非显示项,未在画布(canvas)上显示,但这并不影响对它们的引用,例如,班级块中的班级编号(BJBH),课程块中的课程编号(KCBH)等;

标以课程的块对象是基于视图(view)而非基于表(table)的;

画布上显示的项并非都是基表项(块对象所基于的表或视图中的列),而是通过触发子从其他的表或视图中查询的,这也不影响对它们的引用,例如,课程块中的名称(MC)和学分(XF)等。

图2 Forms模块运行情况

程序实现的步骤如下:

1.在Sql*plus下创建一个与图1所示的表目一致的表(或视图)REPT_001当做Reports模块的查询对象Q_001的虚拟数据源:

CREATE TABLE REPT_001( /* 期末成绩表 */

XH VARCHAR2(9), /* 学号 */

XM VARCHAR2(10), /* 姓名 */

XB VARCHAR2(2), /* 性别 */

XDXZ VARCHAR2(4), /* 修读性质 */

PSCJ NUMBER, /* 平时成绩 */

KSCJ NUMBER, /* 考试成绩 */

ZPCJ NUMBER, /* 总评成绩 */

RKJS VARCHAR2(10) /* 任课教师 */);

注意:该表仅供Reports定义查询对象Q_001之用(仅援引其列名与类型),与整个应用系统的数据库结构无关,不需要向该表插入任何数据(保持该表为空表),不存在数据的完整性和一致性的问题。

2.在Reports中定义报表模块,源模块的名称为REP_001.rdf。在该模块中,定义与表目数据相关的查询对象Q_001:

SELECT XH,XM,XB,XDXZ,PSCJ,KSCJ,ZPCJ,RKJS FROM REPT_001;

再定义与表头数据相关的用户参数:

P_XN1 /* 学年学期 */

P_XN2

P_XQ

P_BJBH /* 班级编号 */

P_BJMC /* 班级名称 */

P_ZYMC /* 专业名称 */

P_KCBH /* 课程编号 */

P_KCMC /* 课程名称 */

P_XF /* 学分 */

P_BJRS /* 班级人数 */

3.在Reports的布局编辑器中调整美化报表的输出格式,最后生成目标模块REP_001.rep。

4.在Forms模块中定义<报表>按钮以实现对Reports模块REP_001.rep的调用。

5.为<报表>按钮定义WHENBUTTONPRESSED触发子:

DECLARE

PL_ID PARAMLIST;

RG_ID RECORDGROUP;

GC_XH GROUPCOLUMN;

GC_XM GROUPCOLUMN;

……

BEGIN

/* 定义用来捆绑表目数据的记录组G_001*/

RG_ID:=FIND_GROUP(‘G_001’);

IF ID_NULL(RG_ID) THEN

RG_ID:=CREATE_GROUP(‘G_001’);

/* 定义记录组内各列,注意各列的列名、类型、顺序均要和Reports模块所定义的查询对象Q_001中的各列相一致 */

GC_XH:=ADD_GROUP_COLUMN(RG_ID,‘XH’,CHAR_COLUMN);

GC_XM:=ADD_GROUP_COLUMN(RG_ID,‘XM’,CHAR_COLUMN);

……

ELSE

DELETE_GROUP_ROW(RG_ID,ALL_ROWS);

GC_XH:=FIND_COLUMN(RG_ID,‘XH’,

CHAR_COLUMN);

GC_XM:=FIND_COLUMN(RG_ID,‘XM’,

CHAR_COLUMN);

……

END IF;

/* 把Forms模块中的数据传送到记录组中 */

GO_BLOCK(‘CJZB’);

FIRST_RECORD;

ROW_NUM:=1;

FOR LOOP

ADD_GROUP_ROW(RG_ID,END_OF_GROUP);

SET_GROUP_CHAR_CELL(GC_XH,ROW_NUM,:CJZB.XH);

SET_GROUP_CHAR_CELL(GC_XM,ROW_NUM,:CJZB.XM);

……

EXIT WHEN :SYSTEM.LAST_RECORD=‘TRUE’;

LOW_NUM:=LOW_NUM+1;

NEXT_RECORD;

END LOOP;

/* 建立参数表 */

PL_ID:=GET_PARAMETER_LIST(‘CJ_LIST’);

IF NOT ID_NULL(PL_ID) THEN

DESTROY_PARAMETER_LIST(‘CJ_LIST’);

END IF;

PL_ID:=CREATE_PARAMETER_LIST(‘CJ_LIST’);

/* 通过向参数表中加入文本参数的方式传送表头数据。其中,第1个参数是参数表标识符,第2个参数是键(参数名),应与Reports模块中定义的用户参数同名,第3个参数指定参数的种类是文本参数,第4个参数引用Forms模块的数据项 */

ADD_PARAMETER(PL_ID, ‘P_XN1’,

TEXT_PARAMETER,:START.XN1);

ADD_PARAMETER(PL_ID, ‘P_XN2’,

TEXT_PARAMETER,:START.XN2);

……

/* 向参数表传送表目数据。其中,第1个参数是参数表标识符,第2个参数是键(参数名),应与Reports模块中定义的查询对象同名,第3个参数指定参数的种类是数据参数,第4个参数引用Forms模块的记录组 */

ADD_PARAMETER(PL_ID, ‘Q_001’,

DATA_PARAMETER,‘G_001’);

/* 调用报表模块REP_001 */

RUN_PRODUCT(REPORTS,‘C:\Apps\REP_001’,SYNCHRONOUS,RUNTIME,FILESYSTEM,PL_ID);

END;

小 结

通过上述方法,借助参数表实现Forms模块对Reports模块的集成,既能快速生成报表(包括实时的业务单据的套打),又提高了系统的效率。如果巧妙地将报表标题、栏目标题、表头数据、格式线段、表目数据等均通过参数传送,并借助Reports的格式触发子和SWR包的模块(过程、函数和例外处理),还可以生成各种复杂的动态报表,免除了使用第三方工具带来的麻烦和副作用(如增加新的连接等)。