丁保国的博客

收集整理工作生活,点点滴滴

  :: :: 博问 :: 闪存 :: :: :: :: 管理 ::

第1章             主副明细

所谓主副明细(Master/Detail)就是指父子表之间的关联性。在一个数据库中往往同时存在许多表,这些表的数据内容可能是毫不相干的,但也可能是息息相关的。例如:商品信息表含有“商品代码”字段,销售记录表中也含有“商品代码”字段,则这两个表因此产生了关联。商品信息表称为父表(主表),销售记录表称为子表(从表)。

1.1             主副明细

CH08中的范例数据库DEPT.mdb包含两张表:主管名单、员工名单

 

61  主从明细表

这两张表存在着主副关系,其中“部门代号”为关联字段。

问:我们能否由“主管名单”表中选取某位主管,而得知该主管所属部门内的所有员工的数据列表?即:实现子表跟随主表记录变化而变化。

 

步骤:

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属性

 

62  通过建立主副明细关联实现子表跟随主表记录变化而变化

 

3、设置子表的MasterFields属性

 

63  设置子表的MasterFields属性

4、运行程序,可以看到:当用鼠标单击DBGrid1中不同的行(或按光标键)以改变ADOTable1的记录位置时,DBGrid2中的内容也跟着同步改变,从而实现了子表记录跟随主表记录的改变而改变。

 

 

 

问:如果要求用列表框或组合框来代替DBGrid1,又如何实现呢?

 

 

提示:使用DBLookupListBoxDBLookupComboBox构件。(注意:不是DBListBoxDBComboBox),并按下表设置DBLookupListBox1的相关属性:

 

构件

属性

属性值

DBLookupListBox1

KeyField

部门代码

ListField

主管姓名

ListSource

DataSource1

 

其余操作步骤同使用DBGrid时一样。

 

64  主表中的记录使用列表框或组合框来代替DBGrid

 

 

 

【方法一】:使用ADOQueryADODataSet,通过查询实现子表跟随主表记录变化而变化。

 

 

 

65  通过查询实现子表跟随主表记录变化而变化

 

代码如下:

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;

 

 

注意

 

 


    程序写在ADOTable1AfterScroll事件中。这样,当用户单击DBGrid1中不同的行来移动记录指针时,通过ADODataSet1对子表执行查询,来显示子表中相应的记录。

 

 

1.2             增加关联字段

 

在上述实现主副明细的例子中,你有没有发现:在用DBGrid2显示明细表数据时,并没有显示部门名称,而只有部门代号!而我们在查看数据时,一般很难马上得知该代号的部门到底是哪个部门,必须再到“主管名单”表中去查找。

 

66  子表中只显示“部门代号”

 

能不能在明细表中增加一个字段,用于显示员工所属的部门名称呢?答案是肯定的。

步骤:

1、将ADOTable2Active属性设置为False

2、双击与明细表对应的ADOTable2,打开“字段编辑器”,在此窗口中单击右键打开快捷菜单,选择其中的“New fields”,打开“New field”对话框。

 

67  在字段编辑器中添加新字段

 

3、在“New field”对话框中进行如下设置:

 

68  新字段设置对话框

 

 

 

4、点击OK,并将ADOTable2Active属性设置为True ,就能看到结果。

 

69  运行结果

 

进一步改进界面:

上述 6‑6中,似乎没有必要占用DBGrid中的一列来显示同一个部门名称,甚至“部门代号”列也可以从DBGrid中删除,而改用一个DBEditDBText来显示部门名称足矣!

 

610  DBEdit来显示部门名称

 

读者可自行完成。

1.3             使用ADODataSet组件创建主副明细窗体

 

前面我们是使用ADOTable组件来完成主副明细表的关联操作的。ADOTable组件是以单个表为操作主体,但有时可能需要将几个表中的数据组织到一起,这时就需要使用ADODataSetADOQuery组件。

61JXGL数据库中,要设计如 611的一个窗体:

要求在DBGrid1中选择某一某个学生(同时显示系名与专业名称),在DBGrid2中显示该学生所学课程的成绩(同时显示学号与课程名称)

 

611  ADODataSet组件的主副明细

分析:

JXGL数据库中包含5张表,它们之间的关系见 612

 

612  JXGL数据库中表之间的关系

由于DBGrid1中除了要显示学生的学号、姓名,还要显示系名与专业名称,所以DBGrid1的数据来源必须来自三张表:学生、系代码、专业代码。这就要用查询语句SelectSQL来完成。

同样,DBGrid2中的数据也要来自于二张表:成绩、课程,所以也要用查询语句SelectSQL来完成。

所以,DBGrid1DBGrid2的数据来源都必须基于查询而非某个表。

 

操作步骤:

1ADODataSet1CommandText属性设置为如下SQL命令:

 

SELECT 学生.学号, 学生.姓名, 系代码.系名, 专业代码.专业名称

FROM 专业代码 INNER JOIN (系代码 INNER JOIN 学生 ON 系代码.系代号 = 学生.系代号) ON 专业代码.专业代号 = 学生.专业代号

 

2ADODataSet2CommandText属性设置为如下SQL命令:

 

SELECT 成绩.学号, 课程.课程名, 成绩.成绩

FROM 课程 INNER JOIN 成绩 ON 课程.课程代号 = 成绩.课程代号

 

 

注意

 

 


    上述两条SQL命令的查询输出字段中必须包含“学号”作为两个查询的关联字段。

3ADODataSet2DataSource属性:DataSource1 ; MasterFields属性:学号 。 613注意:这一步与6.1的设置有所不同!

 

613  ADODataSet组件的属性设置

4、运行程序,即可看到结果。

 

1.4             使用ADOQuery组件创建主副明细体

 

将上一节例子中的ADODataSet2改为ADOQuery,又如何做呢?这里碰到的问题是:ADOQuery组件没有MasterFields属性 !

 

解决方法:

 

设置ADOQuery1SQL属性如下:

SELECT 成绩.学号, 课程.课程名, 成绩.成绩

FROM 课程 INNER JOIN 成绩 ON 课程.课程代号 = 成绩.课程代号

Where 学号 =:学号

 

其它不用作任何改动。

 

614  ADOQuery组件的主副明细

1.5             应用实例

本节将通过一个实际例子,来讲述如何综合应用前面所学各章知识来创建数据录入模块。

62  根据Northwind数据库创建一个订单录入窗体。见 615

 

615  订单录入界面

1、分析:

Northwind(北风贸易)是一个从事食品进出口业务的贸易公司(本公司纯属虚构),主要业务流程为:

供应商

进货

客户

销售

产品

北风贸易公司

 

 

 

 

 

 

 

 

616  北风贸易公司业务流程

其中,产品的销售都是根据客户订单来进行的。由于客户每次订货,往往并不只限于一种产品,可能有多个产品,即:一张订单中可能包括多种产品,这样Northwind数据库中除了“订单表”之外,还必须有一个“订单明细”表,并且根据“订单ID”字段来决定某一张订单中包含哪些明细记录。

除了订单及订单明细表外,还有其它几张表,如:产品表、客户表、运货商表、雇员(即:本公司员工)表,它们之间的关系见 617

 

617  Northwind数据库中各表之间关系

“订单”表与“订单明细”表之间设置参照完整性规则,见 618

 

618  参照完整性规则

2、界面设计

公司实际的订单一般大致如 619所示,表格中内容就是订单明细。

 

619  订单格式

程序界面基本上根据实际的订单设计:窗体上半部分为“订单”表中各项目,只有一项“运费”放在右下角;下半部分为“订单明细”表中各项目;窗体右下角显示“订单明细”中的货款小计,合计则是小计与运费之和。

 

620  设计订单录入界面

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、    62存在着几个缺陷:

1)、增加“订单明细”记录时,如果操作完毕,在关闭窗体之间没有在DBGrid中移动光标离开新增的那条记录,则新增的记录不会被保存。

2)、缺少“订单明细”记录的删除。

请根据以上提示完善 62

2、    根据Northwind数据库,设计如下的产品录入窗体。

 

 

 

posted on 2007-07-21 15:29  丁保国  阅读(560)  评论(0)    收藏  举报