第1章 主副明细
所谓主副明细(Master/Detail)就是指父子表之间的关联性。在一个数据库中往往同时存在许多表,这些表的数据内容可能是毫不相干的,但也可能是息息相关的。例如:商品信息表含有“商品代码”字段,销售记录表中也含有“商品代码”字段,则这两个表因此产生了关联。商品信息表称为父表(主表),销售记录表称为子表(从表)。
1.1 主副明细
CH08中的范例数据库DEPT.mdb包含两张表:主管名单、员工名单
图 6‑1 主从明细表
这两张表存在着主副关系,其中“部门代号”为关联字段。
问:我们能否由“主管名单”表中选取某位主管,而得知该主管所属部门内的所有员工的数据列表?即:实现子表跟随主表记录变化而变化。
步骤:
1、按图 6‑2布置好程序界面,并设置各组件的属性:
|
构件 |
属性 |
属性值 |
|
ADOConnection1 |
ConnectionString |
Provider=Microsoft.Jet.OLEDB.4.0;Data Source=E:\Delphi数据库程序设计\教材例子(光盘)\CH08\Dept.mdb;Persist Security Info=False |
|
ADOTable1 |
Connection |
ADOConnection1 |
|
TableName |
主管名单 |
|
|
Active |
True |
|
|
DataSource1 |
DataSet |
ADOTable1 |
|
DBGrid1 |
DataSource |
DataSource1 |
|
ADOTable2 |
Connection |
ADOConnection1 |
|
TableName |
员工名单 |
|
|
Active |
True |
|
|
DataSource2 |
DataSet |
ADOTable2 |
|
DBGrid2 |
DataSource |
DataSource2 |
2、设置子表的MasterSource属性
图 6‑2 通过建立主副明细关联实现子表跟随主表记录变化而变化
3、设置子表的MasterFields属性
图 6‑3 设置子表的MasterFields属性
4、运行程序,可以看到:当用鼠标单击DBGrid1中不同的行(或按光标键)以改变ADOTable1的记录位置时,DBGrid2中的内容也跟着同步改变,从而实现了子表记录跟随主表记录的改变而改变。
|
问:如果要求用列表框或组合框来代替DBGrid1,又如何实现呢?
|
提示:使用DBLookupListBox或DBLookupComboBox构件。(注意:不是DBListBox或DBComboBox),并按下表设置DBLookupListBox1的相关属性:
|
构件 |
属性 |
属性值 |
|
DBLookupListBox1 |
KeyField |
部门代码 |
|
ListField |
主管姓名 |
|
|
ListSource |
DataSource1 |
其余操作步骤同使用DBGrid时一样。
图 6‑4 主表中的记录使用列表框或组合框来代替DBGrid
【方法一】:使用ADOQuery或ADODataSet,通过查询实现子表跟随主表记录变化而变化。
图 6‑5 通过查询实现子表跟随主表记录变化而变化
代码如下:
procedure TForm1.ADOTable1AfterScroll(DataSet: TDataSet);
begin
ADODataset1.Close;
ADODataSet1.CommandText:='select * from 员工名单 where 部门代号= :bmdm';
ADODataSet1.Parameters.ParamByName('bmdm').Value:=ADOTable1.fieldbyname('部门代码').AsString;
ADODataSet1.Open;
end;
程序写在ADOTable1的AfterScroll事件中。这样,当用户单击DBGrid1中不同的行来移动记录指针时,通过ADODataSet1对子表执行查询,来显示子表中相应的记录。 |
1.2 增加关联字段
在上述实现主副明细的例子中,你有没有发现:在用DBGrid2显示明细表数据时,并没有显示部门名称,而只有部门代号!而我们在查看数据时,一般很难马上得知该代号的部门到底是哪个部门,必须再到“主管名单”表中去查找。
能不能在明细表中增加一个字段,用于显示员工所属的部门名称呢?答案是肯定的。
步骤:
1、将ADOTable2的Active属性设置为False。
2、双击与明细表对应的ADOTable2,打开“字段编辑器”,在此窗口中单击右键打开快捷菜单,选择其中的“New fields”,打开“New field”对话框。
图 6‑7 在字段编辑器中添加新字段
3、在“New field”对话框中进行如下设置:
图 6‑8 新字段设置对话框
4、点击OK,并将ADOTable2的Active属性设置为True ,就能看到结果。
图 6‑9 运行结果
进一步改进界面:
上述图 6‑6中,似乎没有必要占用DBGrid中的一列来显示同一个部门名称,甚至“部门代号”列也可以从DBGrid中删除,而改用一个DBEdit或DBText来显示部门名称足矣!
图 6‑10 用DBEdit来显示部门名称
读者可自行完成。
1.3 使用ADODataSet组件创建主副明细窗体
前面我们是使用ADOTable组件来完成主副明细表的关联操作的。ADOTable组件是以单个表为操作主体,但有时可能需要将几个表中的数据组织到一起,这时就需要使用ADODataSet或ADOQuery组件。
例 6‑1在JXGL数据库中,要设计如图 6‑11的一个窗体:
要求在DBGrid1中选择某一某个学生(同时显示系名与专业名称),在DBGrid2中显示该学生所学课程的成绩(同时显示学号与课程名称)
分析:
JXGL数据库中包含5张表,它们之间的关系见图 6‑12
由于DBGrid1中除了要显示学生的学号、姓名,还要显示系名与专业名称,所以DBGrid1的数据来源必须来自三张表:学生、系代码、专业代码。这就要用查询语句Select-SQL来完成。
同样,DBGrid2中的数据也要来自于二张表:成绩、课程,所以也要用查询语句Select-SQL来完成。
所以,DBGrid1、DBGrid2的数据来源都必须基于查询而非某个表。
操作步骤:
1、ADODataSet1的CommandText属性设置为如下SQL命令:
SELECT 学生.学号, 学生.姓名, 系代码.系名, 专业代码.专业名称
FROM 专业代码 INNER JOIN (系代码 INNER JOIN 学生 ON 系代码.系代号 = 学生.系代号) ON 专业代码.专业代号 = 学生.专业代号
2、ADODataSet2的CommandText属性设置为如下SQL命令:
SELECT 成绩.学号, 课程.课程名, 成绩.成绩
FROM 课程 INNER JOIN 成绩 ON 课程.课程代号 = 成绩.课程代号
上述两条SQL命令的查询输出字段中必须包含“学号”作为两个查询的关联字段。 |
3、ADODataSet2的DataSource属性:DataSource1 ; MasterFields属性:学号 。见图 6‑13(注意:这一步与6.1的设置有所不同!)
4、运行程序,即可看到结果。
1.4 使用ADOQuery组件创建主副明细体
将上一节例子中的ADODataSet2改为ADOQuery,又如何做呢?这里碰到的问题是:ADOQuery组件没有MasterFields属性 !
解决方法:
设置ADOQuery1的SQL属性如下:
SELECT 成绩.学号, 课程.课程名, 成绩.成绩
FROM 课程 INNER JOIN 成绩 ON 课程.课程代号 = 成绩.课程代号
Where 学号 =:学号
其它不用作任何改动。
图 6‑14 ADOQuery组件的主副明细
1.5 应用实例
本节将通过一个实际例子,来讲述如何综合应用前面所学各章知识来创建数据录入模块。
例 6‑2 根据Northwind数据库创建一个订单录入窗体。见图 6‑15
1、分析:
Northwind(北风贸易)是一个从事食品进出口业务的贸易公司(本公司纯属虚构),主要业务流程为:
|
供应商 |
|
进货 |
|
客户 |
|
销售 |
|
产品 |
|
北风贸易公司 |
图 6‑16 北风贸易公司业务流程
其中,产品的销售都是根据客户订单来进行的。由于客户每次订货,往往并不只限于一种产品,可能有多个产品,即:一张订单中可能包括多种产品,这样Northwind数据库中除了“订单表”之外,还必须有一个“订单明细”表,并且根据“订单ID”字段来决定某一张订单中包含哪些明细记录。
除了订单及订单明细表外,还有其它几张表,如:产品表、客户表、运货商表、雇员(即:本公司员工)表,它们之间的关系见图 6‑17。
“订单”表与“订单明细”表之间设置参照完整性规则,见图 6‑18。
2、界面设计
公司实际的订单一般大致如图 6‑19所示,表格中内容就是订单明细。
程序界面基本上根据实际的订单设计:窗体上半部分为“订单”表中各项目,只有一项“运费”放在右下角;下半部分为“订单明细”表中各项目;窗体右下角显示“订单明细”中的货款小计,合计则是小计与运费之和。
图 6‑20 设计订单录入界面
3、各组件相关属性设置
(1)、ADOConnection组件:
因为本例将用到多个数据集组件,为实现这多个组件共用一个数据库连接,所以先在窗体上放置一个ADOConnection组件,并设置好ConncetionString属性。
(2)、ADOTable组件:
需要用到两个:
ADT_Order:用于连结“订单”表
ADT_OrderDetails:用于连结“订单明细”表。
然后以ADT_Order为“主”, ADT_OrderDetails为“副”,将“订单ID”字段作为两个表的关连字段,设定好主副明细关系(详见本章6.2)
除了上述两个具有主副明细关系的ADOTable组件之外,还要用到如下几个ADOTable组件,分别给DBLookupComboBox组件提供列表源(ListSource):
|
ADOTable组件 |
作用 |
|
ADT_Customer |
为DBLookupComboBox 1(“客户名称”)提供列表源。 |
|
ADT_Shipper |
为DBLookupComboBox2 (“运货商”)提供列表源。 |
|
ADT_Employees |
为DBLookupComboBox3 (“雇员”)提供列表源。 |
|
ADT_Products |
为DBGrid的第2列(“产品名称”)提供列表源。 |
(3)、ADODataSet组件:
在这个例子中,ADODataSet组件用于计算一张订单中,客户所订购的产品总金额(扣除折扣部分)。
4、事件处理程序
//处理当用户单击DBNavigator中的“编辑”与“插入”按钮时,让数据感知构件变为无效/有效
procedure TfrmMain.DBNavigator1Click(Sender: TObject;
Button: TNavigateBtn);
var i:Integer;
begin
if (Button=nbEdit) or (Button=nbInsert) then
begin
for i:=0 to GroupBox1.ControlCount-1 do
if (GroupBox1.Controls[i] is TDBEdit) or (GroupBox1.Controls[i] is TDBLookUpComboBox) then
GroupBox1.Controls[i].Enabled:=true
end
else
for i:=0 to GroupBox1.ControlCount-1 do
if (GroupBox1.Controls[i] is TDBEdit) or (GroupBox1.Controls[i] is TDBLookUpComboBox) then
GroupBox1.Controls[i].Enabled:=false;
end;
//当增加或编辑记录后提交时,如果出现错误,显示相应的错误信息
procedure TfrmMain.ADT_OrderPostError(DataSet: TDataSet; E: EDatabaseError;
var Action: TDataAction);
begin
showmessage(e.Message);
Action:=daAbort;
end;
//计算并显示当前订单中,客户所订购产品的总金额
procedure TfrmMain.ADT_OrderAfterScroll(DataSet: TDataSet);
var SubTotal,Freight,Total:real;
begin
ADODataSet1.Close;
ADODataSet1.CommandText:='SELECT 订单ID,Sum(数量*单价*(1-折扣)) as 小计 FROM 订单明细 GROUP BY 订单ID having 订单ID=:OrderID';
ADODataSet1.Parameters.ParamByName('OrderID').Value:=ADT_Order.fieldByName('订单ID').AsString;
ADODataSet1.Open;
SubTotal:=ADODataSet1.FieldByName('小计').AsFloat;
Freight:=ADT_Order.FieldByName('运货费').AsFloat;
Total:=SubTotal+Freight;
Edit1.Text:=Format('%.2f',[SubTotal]);
Edit2.Text:=Format('%.2f',[Total]);
end;
//“显示本月产品”的OnClick事件处理程序
procedure TfrmMain.Button1Click(Sender: TObject);
begin
//留待以后补充
end;
//“打印发货单”的OnClick事件处理程序
procedure TfrmMain.Button2Click(Sender: TObject);
begin
//留待以后补充
end;
说明:“显示本月产品”与“打印发货单”,这两个命令按钮的OnClick事件处理程序需要用到报表设计与制作,故暂时不作任何处理。
课外练习:
(1)、增加“订单明细”记录时,如果操作完毕,在关闭窗体之间没有在DBGrid中移动光标离开新增的那条记录,则新增的记录不会被保存。
(2)、缺少“订单明细”记录的删除。
请根据以上提示完善例 6‑2
2、 根据Northwind数据库,设计如下的产品录入窗体。

浙公网安备 33010602011771号