1.5 表间关联
顾名思义,表间关联就是在多个表之间建立一种联系。
例如,在之前所创建的“产品”“客户”“订单”这3个表中,“订单”表中的每一个订单都分别在“产品”表和“客户”表中有对应的行。也就是说,每一个订单都会对应一个产品和一个客户。同样,“产品”表或者“客户”表中的每一行会在“订单”表中有若干条的对应记录(也就是订单)。如果能够在“产品”表与“订单”表、“客户”表与“订单”表之间建立关联,这些表就可以相互引用数据或者快速查阅关联数据。
1.5.1 设置表间关联
在【数据表】功能区的【表相关】功能组中,单击【关联】按钮。弹出“表间关联”对话框后,再单击【增加】按钮即可设置表间关联,如下图所示。
关于设置关联的一些重要概念说明。
❶父表与子表
在“编辑关联”对话框中,左侧用于设置父表及关联列,右侧用于设置子表及关联列。那么,如何选择父表和子表呢?
仍以“产品”表和“订单”表为例。“订单”表中的每一条记录只能对应“产品”表中的一个产品(记录),而每一个产品在“订单”表中可能对应多个记录(订单),就像一个父亲可以有多个儿子,而一个儿子只能有一个父亲一样,这种一对多的关系就称为父子关系。很明显,在“产品”表和“订单”表之间建立关联时,“产品”表是父表,“订单”表是子表,它们通过“产品ID”列就可建立关联。
在Foxtable中,这种父子关系的约束其实并不是很严格的。也就是说,子表中的一条记录,也可以在父表中对应有多条记录。例如,一个作者可以写多本书,而一本书也可以有多个作者,不过实际应用时这种情况并不多见。
在上图中,父表和子表是通过“产品ID”列进行关联的。两个表的关联列名称未必完全相同,仅仅是本例中“产品”和“订单”表恰好都使用了相同的列名称而已:假如将父表的“产品ID”列改为“产品编号”,那么就可以设置父表的“产品编号”列和子表的“产品ID”列进行关联。尽管父表和子表的关联列名称可以不同,但其数据类型必须相同,且不能把表达式列作为关联列使用。
关联列数量不限,可根据需要随意增加。一般建议关联列的数量不要超过两个。
❷关联选项
关联设置对话框有两个选项,分别是【同步更新关联列内容】和【同步删除关联行】。
● 同步更新关联列内容。
如果选中此复选框,在父表修改某行关联列内容后,子表中对应行的关联列内容会同步更新,使得两个表之间的关联行始终保持联系。例如,“产品”表和“订单”表通过“产品ID”建立了关联,如果修改“产品”表中的“产品ID”,那么“订单”表中相对应的“产品ID”也会自动作出修改。
● 同步删除关联行。
如果选中此复选框,在父表中删除某行,在子表中对应的关联行会同步删除。例如,“产品”表和“订单”表通过“产品ID”建立了关联,如果在“产品”表中删除某产品,那么“订单”表中此产品的全部订单也会被自动删除。
❸设置关联名称
由于这是一个新创建的表间关联,单击【确定】按钮后,系统将提示输入一个关联名称,以方便后期的使用和管理。这个名称是自定义的,其命名规则和表名、列名完全一致,只要自己能够准确地理解其语义即可。例如,产品的英文是“product”,订单是“order”,可以简单地将它们之间的关联命名为“po”。表间关联创建之后,系统会自动生成一个关联表,如下图所示。
其中,显示在父表下方的“产品订单”就是系统自动生成的关联表,这个关联表的名称为父表标题和子表标题的两者相连,因此显示为“产品订单”。此时,在“产品”表中单击不同的记录行时,关联表会同步显示与当前“产品ID”相关联的订单记录。如上图所示,由于父表中的“产品ID”为“P02”,因而关联表就会从子表中调取与“P02”相关联的全部订单记录并给予显示。
❹不同的关联选项对管理表数据的影响
如果在创建关联时,没有选中【同步更新关联列内容】复选框,那么在父表上对关联列的任何修改都不会影响到子表。例如,将父表中的“P02”改为“A02”,这样修改之后,由于子表没有对应的“产品ID”为“A02”的记录,因而关联表不会有任何记录显示,如下图所示。
如果在创建关联时,选中【同步更新关联列内容】复选框,那么在父表修改关联列内容后,子表中相对应的关联内容也会同步更新。例如,在父表中将“P02”改为“A02”,子表中的“P02”也将被同步更改为“A02”,这样就可以使两个表之间的关联行始终保持联系,如下图所示。
需要注意的是,父表中的关联列内容可以修改,但不能将其内容删除,而且也无法删除,子表则没有此限制。例如,想将上图父表中的“A02”单元格内容进行删除是做不到的,只能进行修改,也就是内容不得为空。如果一定要删除,只能删除“A02”的所在行。
同样地,在删除父表中的数据行时,关联表数据会受到【同步删除关联行】选项的影响。其原理与“同步更新关联列内容”相同。
这里还要补充说明一点,由于关联表数据是基于其他表的,在关联表中对所做的任何修改都会同步体现到相对应的数据表中。例如,上图关联表中的“P02”都被改成了“A02”,而这个关联表是基于“订单”生成的,因而“订单”表中的“P02”也都改成了“A02”,如下图所示。
再如,在关联表中增加行时,关联列的内容会自动输入,这个增加的行同样会体现在其对应的数据表中。而且,这种自动输入并不会受到是否选中【同步更新关联列内容】复选框的影响。
如下图所示,由于父表中的关联列内容为“B02”,那么当在关联表中新增数据行时,与其相关联的“产品ID”列会自动填上“B02”;如果设置的关联列有多个,则多个关联列都会自动完成输入。
假如父表和子表都有“产品ID”和“产品名称”列,同时希望在关联表新增行时,这两列都能自动输入,那么在建立父子关联时,仍然只需选择能唯一区分每一行的列(如“产品ID”),无需同时选择“产品ID”和“产品名称”列。如要实现“产品名称”的自动输入,可采用表达式列实现。
1.5.2 关联表生成模式
在关联设置窗口中,还可以设置关联模式,如下图所示。
关联表有3种生成模式,分别为“单向”“双向”“无”。现仍以“产品”表和“订单”表的关联为例。
❶“单向”模式
此为默认模式。在单向生成模式下,“产品”表的底端会出现一个关联表,标题为“产品订单”;但“订单”表的底端并不会出现关联表。
如果希望在“订单”表中单击某个数据行,同样可以查看该订单所对应的产品资料时,可使用双向模式。
❷“双向”模式
在双向生成模式下,无论是父表还是子表,其下方都会出现一个关联表。其中,父表下方的关联表显示的是子表数据,子表下方的关联表显示的是父表数据。
如下图所示,当在“订单”中单击“产品ID”为“P05”的数据行时,关联表会显示与之对应的父表记录。
关联表的标题名称变为“订单产品”,它是通过子表标题与父表标题拼接而成的。
为了更清楚地看到双向模式下的关联表生成情况,再将“客户”表和“订单”表建立双向关联,并将其关联名称命名为“co”(c表示客户“customer”,o表示订单“order”),如下图所示。
确定后就会发现,每个数据表的下方都会出现两个关联表,如下图所示。
在上图中,当在上方的“客户”表中单击不同的数据行时,下方的“客户订单”将显示与之相关联的订单数据,这是因为“客户”表与“订单”表是父子关系,它们通过“客户ID”列建立了关联;当在下方的“客户订单”表中单击不同数据行时,下方的“产品”表又会显示与之相关联的产品数据,这是因为关联表“客户订单”是基于订单表生成的,而“订单”表与“产品”表又通过“产品ID”列建立了双向关联,如下图所示。
由于“客户订单”表中所单击的数据行“产品ID”的值为“P05”,因而这里的“产品”表显示的就是与之相对应的数据记录。
如果不喜欢这种关联表的显示方式(同样的位置只能独立显示一个关联表,查看其他关联表时需单击下面的表名称切换),也可以在关联表中单击鼠标右键,选择快捷菜单【停靠位置】→【独立停靠】命令,这样会更直观一些(关于窗口停靠方面的知识将在下一章学习),如下图所示。
由此可见,双向生成模式可以为使用带来很多便利,使得无论选择哪一个表,所有相关的数据都可以信手拈来。再如,在“产品”表中,单击任意一个产品,可看到与产品相关的所有订单;单击“订单”中的任意一个订单,又能立刻显示与该订单相关的客户资料,如下图所示。
同理,在“订单”表中,单击任意一个订单,即可马上看到与该订单相关联的产品和客户信息,如下图所示。
很显然,如果将双向改为单向,那么就只会在“产品”和“客户”的下面生成两个关联表,“订单”不会生成关联表,也就无法做到双向生成那样的环环相扣、数据信手拈来。但是,双向模式下,随着关联的增加,表的数量也会按平方数增长;若项目中产生太多表,就可能会影响到运行效率。
如上例,分别在“产品”表和“订单”表之间、“客户”表和“订单”表之间建立两个关联,那么在双向生成模式下,系统会产生2×3=6个关联表,加上原来的3个数据表,合计就有9个表;而单向生成模式下只会产生两个关联表,加上原来的3个数据表,合计5个表,远远低于双向生成的表数据量。假如以后再把数据表增加到5个、建立4个关联,而这4个关联都选择了双向生成,那么就将产生4×5=20个关联表,加上原有的5个数据表,合计就有25个表,多么可怕!
当然,实际使用时是否开启双向生成完全根据自身的硬件性能及需求而定,只要不去滥用双向生成就好。
❸“无”模式
如果生成模式选择“无”,那么不管是“产品”表和还是“订单”表,底端都不会出现关联表,这样既不能在选择某产品时查看订购此产品的订单,也不能在选择某订单时查看其对应产品的资料。
你也许会感到奇怪,既然如此,那还建立关联干什么?
其实建立关联,有时并不仅仅是为了方便查看关联数据,更重要的是在关联表之间相互进行数据的引用和计算。如果建立关联的目的仅仅只是为了引用或统计数据,那么就直接将生成模式选择为“无”吧!
1.5.3 多级关联与同表关联
❶多级关联
如果“表A”和“表B”建立了关联,“表B”和“表C”建立了关联,也就是A是B的父表,B又是C的父表,三者的关系类似于“父(表A) →子(表B) →孙(表C)”这样的关系。
在双向生成模式下,将生成6个关联表,分别如下。
“表A”生成两个关联表:“表A表B”、“表A表B表C”。
“表B”生成两个关联表:“表B表A”、“表B表C”。
“表C”生成两个关联表:“表C表B”、“表C表B表A”。
在单向生成模式下,将生成3个关联表,分别如下。
“表A”生成两个关联表:“表A表B”、“表A表B表C”。
“表B”生成一个关联表:“表B表C”。
“表C”不生成任何关联表。
由此可见,单向生成并非每个关联只生成一个关联表,而是按照“父 → 子 → 孙”这样的路径逐级生成;而双向生成除了这条路径外,还多了反向的“孙 → 子 → 父”的路径,所以生成的关联表数量至少要翻倍。
❷同表关联
表间关联一般是将两个不同的表通过关联列联系起来。在实际建立关联时,父表和子表也可以是同一个表,这就是“同表关联”。使用同表关联时需要注意以下几点。
第一,双向生成对于同表关联无效,即使选择双向生成,Foxtable还是会按照单向生成模式生成关联表。
第二,建立同表关联时父表和子表的关联列必须不同。
例如,有一个“员工表”,其中有一列名为“上级”,用于输入此员工的直接上司的姓名;另有一列为“姓名”列,用于输入全部员工的姓名(包括所有的上级)。
现在新建一个关联,父表为“员工表”,关联列为“姓名”;子表同样是“员工表”,关联列为“上级”,这样在单击某个员工时会自动列出此员工的所有直接下属,如下图所示。
但是,如果在父表中单击除姓名为“王兵”以外的其他行时,关联表内容都为空。这是因为,其他员工姓名没有出现在“上级”列中。
同表关联在实际工作中极少用到,了解即可。
1.5.4 关联表的数据引用与统计
仍以之前的“产品”“客户”和“订单”表为例。假如已经创建了两个关联:一个是“产品”与“订单”通过“产品ID”列建立的关联,关联名称为“po”;另一个是“客户”与“订单”通过“客户ID”列建立的关联,关联名称为“co”。由于这里的关联仅仅用于不同表之间的数据引用和统计,因此在建立以上两个关联时,生成关联表的模式选择为“无”。
❶引用父表数据
在“po”和“co”两个关联中,“订单”表都是作为子表出现的。在这个表中,不论是“产品ID”还是“客户ID”,都只能显示具体的ID号。如果要在此表中同时显示具体的产品名称或客户名称,就可以通过引用父表数据的方法实现。
首先,在“订单”表中添加一个表达式列,列名为“产品名称”,然后编辑列表达式,如下图所示。
在该生成器对话框中,可选择的表有3个:一个是“订单”,也就是当前表;另外两个分别是“Parent(po)”和“Parent(co)”,也就是与当前表存在关联关系的两个父表。其中,“Parent(po)”表示产品表(关联名称为po),“Parent(co)”表示客户表(关联名称为co)。
由于要引用“产品”表中的数据,因而这里就选择“Parent(po)”。选择该父表后,列表框将显示该表中的所有列名称,如下图所示。
如果要引用该表中的“产品名称”数据,只需双击“Parent(po)产品名称”即可。
表达式设置完成后单击【确定】按钮,“订单”表将自动增加一个表达式列“产品名称”,该列的内容就取自产品表中的“产品名称”。同理,再增加一个表达式列“客户名称”,其内容取自客户表的“客户名称”列,如下图所示。
如此一来,“订单”表不仅能显示“产品ID”“客户ID”,也能同时显示它们对应的“产品名称”和“客户名称”了。
在引用父表数据时,如果当前表只有一个父表,列表达式可以简写为“Parent产品名称”或“Parent客户名称”。由于本例的订单表存在两个父表,因此“po”或“co”是不能省略的,必须通过关联名称来指定从哪个父表中引用数据。
此外,对于这种父表与子表之间的数据引用,父子之间必须是严格的一对多关系。理由很明显,如果不是一对多的关系,而是多对多的关系,那么子表在引用数据时,就不知道该引用父表中哪一条记录的数据了,从而导致错误。
❷统计子表数据
假如想在父表中统计相关联的子表数据,也是一样的处理方法。例如,现在想在“产品”表中统计不同产品的销售数量,可以在“产品”表中增加一个名称为“数量”的表达式列,如下图所示。
需注意,这里的表达式使用了聚合函数Sum,表示对指定的列求和:Sum(Child(po)数量)。这里的Child表示的是子表。
由于“产品”表只有一个子表,因此,该表达式也可以简写为“Sum(Child数量)”。
用同样的方法,也可以再增加一个“金额”表达式列,对子表中的“金额”进行统计。统计结果如下图所示。
列表达式可用的其他聚合函数还有Avg(平均)、Min(最小值)、Max(最大值)、Count (计数)、St Dev(标准偏差)、Var(方差),如在“客户”表中再增加下图所示的列表达式。
上图中所有的表达式列都是新增的,列表达式在右侧。执行后的效果如下图所示。
必须说明的是,通过聚合函数统计子表数据,只能是无条件的。但在实际工作中,有条件的统计非常普遍,怎样才能实现条件统计呢?这就需要采取一种变通的方法。
比如,“订单”表中有个用于记录“是否付款”的逻辑列,已付款的为True,未付款的为False。如果想在“产品”表中统计“已付款”和“未付款”的订单金额,就可以先在“订单”表中增加一个表达式列“已付款金额”,此列可作为统计过渡列使用。其表达式为:
IIF([是否付款] = True,[金额],Null)
也可简写为:
IIF([是否付款],[金额],Null)
执行后的效果如下图所示(如觉得此过渡列在“订单”表中显得多余,可将其隐藏)。
然后再在“产品”表中增加两个表达式列对此过渡列进行统计。这两个表达式列一个是“已付款金额”,一个是“未付款金额”。它们的表达式分别为:
Sum(Child(po).已付款金额)
Sum(Child(po).金额) - Sum(Child(po).已付款金额)
执行效果如下图所示。
是不是觉得有点麻烦且不太方便?确实如此,毕竟表达式所支持的函数有限,而且限制比较多,对于一些较为复杂的统计要求,有时会显得无能为力。
其实,Foxtable有两种方式来实现计算和统计:一种是利用刚才的表达式;另一种就是利用代码。表达式使用简单,计算速度快,即使10万行的计算也可以瞬间完成,而且不占据存储空间,仅仅在运行时生成计算结果;而用代码计算具备无比的灵活性,可以随心所欲,只要代码写得合理,运行速度一样快捷,但这就需要用到编程知识了。
关于编程,这是Foxtable作为开发平台使用时才用到的知识,本书第2篇将专门学习。