摘 要: 为减少数据冗余,根据规范化理论设计的数据库不能直接输出分类汇总的结果,需要进行转换生成交叉表,并利用水晶报表的Push模式实现对交叉表的显示。在.NET和SQL Server环境下,探讨了动态交叉表的生成和利用水晶报表动态绑定数据源显示数据的方法,并给出了较为详细的实现过程。
关键词: 动态交叉表;水晶报表;Push模式;动态绑定
0 引言
信息系统中都不能缺少报表,而交叉表是种常见的报表形式,它将源表中的数据分组汇总后,一组列在表的左侧,另一组列在表的上部,从而形成一种分类汇总表格[1]。动态交叉表是按用户呈现数据的要求,不仅对数据进行分类,同时还要根据表中数据的情况动态创建列,把数据行信息置换到表格列处并进行汇总。
水晶报表是一款商务智能软件,主要用于设计产生报表,是业内功能最强的报表系统[2],其出现的目的就是使计算机参与到办公系统业务流程中。
1 问题的提出
以我校学生成绩管理系统为例,为消除存储异常,减少数据冗余,保证数据的完整性,按规范化设计理论设计的数据库含多张表,其中与成绩有关的3个表的关系模式学生信息表S(学号、姓名、性别、班号)、课程信息表C(课号、课名)、学生成绩表SC(学号、课号、学期、成绩)之间的关系图如图1所示。学生成绩表中的数据形式如图2所示。实际工作中需要打印的学生成绩表如图3所示。
由此可见,数据库中存储的数据,在某些应用中,需要生成动态交叉表,因不同班级不同学期学生学习的课程不一样,生成的动态交叉表的列项的个数和名称都是不固定的。经验表明,水晶报表虽然功能强大且使用方便,但它要求设计表格时所使用的表名以及列名与使用时必须一致。本文研究了水晶报表的Pull模式和Push模式[3],提出了在数据库端生成一个表格列项固定的动态交叉表,利用Push模式动态绑定数据源把数据推送给水晶报表引擎的方法。
2 生成动态交叉表
首先在数据库服务器端完成列项名称与个数固定的动态交叉表的生成。设每学期最多有8门课,实现方法如下:
(1)创建函数ufGetCourse,功能是筛选出某班对应学期的课程,并按顺序编号。运行结果如图4所示。
create function ufGetCourse(@bno int,@tnum int)returns table
as return
(
select col=count(*),tc1.cno,cname
from(select distinct cno from s join sc on s.sno=sc.sno where bno=@bno and tnum=@tnum)as tc1
join(select distinct cno from s join sc on s.sno=sc.sno where bno=@bno and tnum=@tnum)as tc2
on tc1.cno>=tc2.cno
join c on tc1.cno=c.cno
group by tc1.cno,cname
)
(2)创建存储过程upCreateJCB,功能是按学号对应,把顺序编号为1的4号课程成绩放在Cj1列中,把顺序编号为2的5号课程成绩放在Cj2列中,以此类推,最后计算每个学生的总评分。运行结果如图5所示。
create proc upCreateJCB
@bno int,@tnum int
as
declare @str varchar(100),@cstu varchar(100),@cno int,@sno char(10),@sname varchar(50),@sc int,@col int,@ZF int
create table #t
(
sno char(10),sname varchar(50),ZF decimal(5,1),
cj1 int,cj2 int,cj3 int,cj4 int,cj5 int,cj6 int,cj7 int,cj8 int
)
declare my_C cursor for select s.sno,sname,col,sc.cno,Score from S join SC on s.sno=sc.sno join(select*from dbo.ufGetCourse(@bno,@tnum))as c on sc.cno=c.cno where bno=@bno and tnum=@tnum order by sno
open my_C
fetch next from my_C into@sno,@sname,@col,@cno,@sc
while @@fetch_status=0
begin
select@cstu=@sno,@ZF=0
insert into#t(sno,sname)values(@sno,@sname)
while@@fetch_status=0 and@cstu=@sno
begin
set@str=′update#t set cj′+cast(@col as varchar(2))+′=′′′+cast(@sc as varchar(3))+′′′′
set@str=@str+′where sno=′+@sno
exec(@str)
set@ZF=@ZF+@sc
fetch next from my_C into@sno,@sname,@col,@cno,@sc
end
--更新总评分
update #t set ZF=@ZF where sno=@cstu
end
select*from#t
drop table#t
close my_C
deallocate my_C
3 水晶报表动态绑定数据源
应用程序的前台界面在.NET平台下进行水晶报表设计,采用Push模式在程序中动态加载数据源和报表,用动态传参方式把表头的cj1~cj8更换成对应的中文课程名,方法如下:
(1)建立解决方案。在解决方案资源管理器中添加“Crystal报表”模板。方法为:添加→新建项→Crystal报表→命名报表为MyCry.rpt→作为空白报表。
(2)在字段资源管理器中通过“报表专家”完成报表设计。方法为:数据库字段→数据库专家→创建新链接→OLE DB(ADO)→Microsoft OLE DB Provider for SQL Server→填写链接数据库的信息→选择对应的数据库→选择存储过程upCreateJCB→把涉及的字段拖拽到水晶报表细节栏中并填上表格线。
(3)在字段资源管理器中添加参数字段:班级名classname、学期TermNo、Cj1~Cj8,并把这些参数字段拖拽到水晶报表的页眉栏处。
经过上述过程设计的报表MyCry.rpt如图6所示。
(4)在窗体上放一个CrystalReportViewer控件并命名为crv,用C#编程动态加载数据源和报表。代码如下:
//程序开始处需对两个名字空间进行引用
using CrystalDecisions.Shared;
using CrystalDecisions.CrystalReports.Engine;
//Load事件下执行的代码
int num,bj=197,xq=1;//197为班级号,1为第1学期
SqlConnection con=new SqlConnection();
con.ConnectionString="Data Source=.;Initial Catalog=Student_Score;Integrated Security=True";
con.Open();
string strSql="exec upCreateJCB"+bj.ToString()+","+xq.ToString();
SqlDataAdapter da=new SqlDataAdapter(strSql,con);
DataTable d1=new DataTable();
da.Fill(d1);
MyCry ocr=new MyCry();
ocr.Load("MyCry");
ocr.SetDataSource(d1);
//向水晶报表中传参数
ParameterFields_ps=new ParameterFields();
//TermNo参数
ParameterField_p=new ParameterField();
ParameterDiscreteValue_v=new ParameterDiscreteValue();
_p.ParameterFieldName="TermNo";
_v.Value=xq;
_p.CurrentValues.Add(_v);
_ps.Add(_p);
//ClassName参数
_p=new ParameterField();
_v=new ParameterDiscreteValue();
_p.ParameterFieldName="ClassName";
_v.Value=bj;//此处可先从班级表中读取班级名然后传递班级名,本例略
_p.CurrentValues.Add(_v);
_ps.Add(_p);
//n个课程名称参数
strSql="select*from dbo.ufGetCourse("+bj.ToString()+","+xq.ToString()+")";
SqlDataAdapter db=new SqlDataAdapter(strSql,con);
DataTable d2=new DataTable();
db.Fill(d2);
for(num=1;num<=d2.Rows.Count;num++)
{
_p=new ParameterField();
_v=new ParameterDiscreteValue();
_p.ParameterFieldName="cj"+num.ToString();
_v.Value=d2.Rows[num-1][2].ToString();
_p.CurrentValues.Add(_v);
_ps.Add(_p);
}
//报表中共有8列,不足8列时后面内容填空
for(;num<=8;num++)
{
_p=new ParameterField();
_v=new ParameterDiscreteValue();
_p.ParameterFieldName="cj"+num.ToString();
_v.Value="";
_p.CurrentValues.Add(_v);
_ps.Add(_p);
}
crv.ParameterFieldInfo=_ps;
crv.ReportSource=ocr;
4 结论
本文提出了利用水晶报表显示并打印动态交叉表的一种方法,图3就是本校学籍管理系统中用本方法打印的成绩汇总表,由于来源于真实数据,故姓名处进行了处理。
参考文献
[1] 张贤斌,费树岷.管理信息系统中动态交叉表的实现方式研究[J].计算机应用工程技术,2008,4(4):995-996.
[2] 丛凤侠,杨玉强.通用水晶报表自动生成技术研究[J].计算机技术与发展,2013,23(5):54-57.
[3] 钱哨,李挥剑,李继哲,等.C#WinForm实践开发教程[M].北京:中国水利水电出版社,2010.