C++ Builder下数据库报表Master/Detail关系功能的实现

主从复合结构（Master/Detail）是基于“一对多”的关系，在一个数据库表中提供详细的信息，而这个表是通过另一个数据库表的外来关键字访问相关记录的。基于主从复合结构，我们可以在浏览一个表中的数据时，同时给出另一个表中与这个记录相关的所有记录信息。
Borland C＋＋Builder提供了TTable 和TQuery类型的数据库控件，可以方便地实现数据库表的Master/Detail关系，本文即以BCB中自带的示例数据库BCDEMOS为例来说明如何采用不同方法实现数据表的主从复合结构关系，以数据浏览功能为例：即在浏览主数据表Customer.db（客户信息）记录的同时，显示从数据表Orders.db（客户定单信息）中与其相关的所有记录的详细信息。
TTable控件相关的基本属性简介如下：DatabaseName:设置要打开的数据库别名或数据库目录路径；TableName:设置所要关联打开的数据库表文件名；Active:设置为true时数据库表文件自动打开，否则需要用代码在程序中打开数据表。TQuery控件的基本属性：DatabaseName:设置要打开的数据库别名或数据库目录路径；SQL:Tstring类型，所要执行的SQL数据查询语句，可以直接在对象观察器（Object Inspector）中双击打开SQL属性进行编辑，Active:设置为true时自动打开查询数据库表文件，否则需要用代码在程序中打开查询数据表。两者与M/D相关的属性将在下边结合示例加以解释。
一、 TTable控件关联主、从表实现Master/Detail关系报表
Master/Detail关系最简单的实现方法是用两个TTable控件分别与主表及从表关联。分别起名为TableMaster和TableDetail，设置TableMaster的DatabaseName为BCDEMOS，TableName为Customer.db；设置TableMaster的DatabaseName为BCDEMOS，TableName为Orders.db。如此即可分别关联上主从数据表。
因为要浏览显示数据表记录内容，所以需要在窗体上放置两个TDBGrid类型的控件DBGridMaster、DBGridDetail以显示M/D关系主从表的相应记录内容；放置两个TDataSource类型的控件DataSourceMaster、DataSourceDetail以指明数据源。设置DataSourceMaster的DataSet属性为TableMaster，DataSourceDetail的DataSet属性为TableDetail，分别指向主从数据表。设置DBGridMaster的DataSource属性为DataSourceMaster，DBGridDetail的DataSource属性为DataSourceDetail。
实现Master/Detail关系的关键在于从表关联控件TableDetail的MasterSource属性和MasterFields属性：前者指向了一个TDataSource控件，该控件DataSet属性应该指向Master/Detail关系的Master表；后者则指定主表和从表的关联字段，需要双击打开“Field Link Designer”对话框进行设置工作，选择从表和主表的相应关联字段添加到“Joined Fields”（关联字段）中即可。本例中以主表（Customer.db客户信息）及从表（Orders.db定单信息）的CustNo（客户号）字段为关联字段，故设置TableDetail的MasterSource属性为DataSourceMaster，指向主表；MasterFields属性为CustNo关联字段。
如果TableMaster和TableDetail的Active属性为false，则需双击窗体Form1空白处，添加以下两句黑体字代码到TForm1::FormCreate（）事件句柄中去：
TableMaster－>Active = true； TableDetail－>Active = true；
运行程序，即可在DBGridMaster、DBGridDetail中浏览到Master/Detail关系主从表的相关数据记录。
二、 TQuery控件实现Master/Detail关系报表
TQuery控件和TTable控件之间的主要差别在于TQuery控件通过SQL属性所赋的SQL指令语句来动态访问数据库，TTable控件则是静态和数据表相关联。TQuery控件可以同时对多个数据库表进行关联访问，TTable控件则只能关联查询单一的数据库表。和TTable控件相比，TQuery控件因为SQL语言的灵活性和相对复杂性，更适合应用在多层、大型、网络数据库系统中。
2.1、 TTable控件关联主表、TQuery控件关联从表实现Master/Detail关系报表
窗体Form1上删去TableDetail控件，放置TQuery类型控件QueryDetail，修改DataSourceDetail的DataSet属性为QueryDetail，其余控件属性不变。TQuery控件可以对访问范围设定限制条件，依此即可实现M/D关系的功能。设定QueryDetail的DatabaseName为BCDEMOS，SQL属性为：
“ Select OrderNo,CustNo,SaleDate,EmpNo From Orders Where Orders.CustNo=:CustNo”。
即可取出从表中所有CustNo字段与主表CustNo字段相同的记录集并且只显示四个限定的字段信息实现Master/Detail关系，另一关键在于从表关联控件QueryDetail的DataSource属性和Params属性：前者指向了一个TDataSource控件，该控件DataSet属性应该指明SQL指令参数的数据来源；后者则设定SQL指令中的参数，需要双击打开“QueryDetail Parameters”对话框进行设置工作，选择相应的SQL指令中参数设置正确即可。本例中以主表（Customer.db客户信息）的CustNo（客户号）字段作为SQL指令的参数，故设置QueryDetail的DataSource属性为DataSourceMaster，指向主表；Params属性为CustNo关联字段作参数。
注：SQL指令中参数名前一定要加冒号作为前缀，以加以区分。
添加以下两句黑体字代码到TForm1::FormCreate（）事件句柄中去：
TableMaster－>Active = true；QueryDetail－>Active = true；
运行程序，即可在DBGridMaster、DBGridDetail中浏览到Master/Detail关系主从表的相关数据记录，注意从表的栅格数据记录只显示有限定的四个字段信息。
若QueryDetail的SQL属性为：“ Select ＊ From Orders Where Orders.CustNo = :CustNo ”，不限定从从表取出的字段名。
2.2、 TQuery控件关联主、从表实现Master/Detail关系报表
Master/Detail关系更灵活的实现方法是主表及从表都与TQuery控件关联，分别起名为QueryMaster和QueryDetail。设置QueryMaster和QueryDetail的DatabaseName都为BCDEMOS；QueryMaster的SQL属性为：“ Select ＊ From Customer ”，DataSource和Params属性均为空，如此即可关联上主数据表，得到和用TableMaster控件一样的效果；QueryDetail的其余属性和2.1中相同。相应的，分别改DataSourceMaster和DataSourceDetail的DataSet属性指向QueryMaster和QueryDetail。
注：由于TQuery控件是以动态方式访问数据表的，故在只用TQuery控件关联主从表时，设计时从表关联控件QueryDetail的Active属性必须设置为false，否则运行时BDE会报告出错信息。
添加以下两句黑体字代码到TForm1::FormCreate（）事件句柄中去：
QueryMaster－>Active = true；QueryDetail－>Active = true；
运行程序，即可在DBGridMaster、DBGridDetail中浏览到与2.1相同效果的Master/Detail关系主从表相关记录信息。
三、单表情况下TQuery控件实现Master/Detail关系汇总、分类报表
在流水作业系统或实时监控系统中，常常要求实时存储当前记录信息到单一的数据表文件中去，而事后再对其进行关系汇总、分类、入库等工作。TTable控件因为只能对单表进行操作，所以在这种单表情况下实现Master/Detail关系汇总、分类等功能的要求只能用TQuery控件实现。仍以Orders.db（定单信息表）举例说明如何灵活利用TQuery控件的SQL指令属性。
在窗体Form1上放置TQuery 控件QueryMaster和QueryDetail，其它的控件属性不变。设置QueryMaster和QueryDetail的DatabaseName都为BCDEMOS；QueryMaster的SQL属性为：
“ SELECT DISTINCT CustNo, SUM( ItemsTotal ) ItemsTotalAll,SUM( Freight ) FreightAll, SUM( AmountPaid ) AmoutPaidAll FROM Orders GROUP BY CustNo ORDER BY CustNo ”，DataSource和Params属性均为空，产生M/D关系中的主表；QueryDetail的的SQL属性设为：
“ Select OrderNo,CustNo,PaymentMethod,ItemsTotal,TaxRate,Freight,AmountPaid From Orders Where Orders.CustNo = :CustNo ”，DataSource属性为DataSourceMaster，指向主表；Params属性以CustNo关联字段作参数。相应的，DataSourceMaster和DataSourceDetail的DataSet属性分别指向QueryMaster和QueryDetail。
示例程序的SQL指令实现了以下功能：以表中每个客户的客户号、定货总值、货运总费、付款总数为记录的字段，从定单信息表中提取出相应信息汇总作为主表；以表中每个定单记录的定单号、客户号、付款方式、提货价值、税率、运费、付款数为记录的字段，从定单信息表中提取相应的信息作为从表；主从表的关联字段CustNo（客户号）通过SQL指令的参数来传递。最终的结果即实现了在DBGridMaster控件上显示出每个客户总的定货信息，在DBGridDetail控件上显示出相应客户的限定字段的详细定货信息记录的功能。
添加以下两句黑体字代码到TForm1::FormCreate（）事件句柄中去：
QueryMaster－>Active = true；QueryDetail－>Active = true；
运行程序，即可在DBGridMaster、DBGridDetail中浏览到Master/Detail关系主从表相关记录信息。
综上所述，对于一般的Master/Detail关系应用，用TTable控件就足以应付了，而要实现更进一步的功能，如多层M/D关系、远程网络数据库操作、多表互关联类型、单表M/D关系的数据库编程,则需要用到TQuery控件，利用SQL指令的灵活性对数据库进行操作。复杂功能的Master/Detail关系数据库编程可在本文的基础上参考相应的书籍以及BCB的联机帮助实现。
本文程序在C＋＋ Builder 3.0/ PWin95，C＋＋ Builder 4.0/ PWin98下调试通过。
(西安 SkyRabbit)