二. 等值 JOIN 的分类
我们来考察下面三种等值 JOIN:
1. 外键关联
表 A 的某个字段和表 B 的主键字段关联(所谓字段关联,就是前一节说过的在等值 JOIN 的过滤条件中要对应相等的字段)。A 表称为事实表,B 表称为维表。A 表中与 B 表主键关联的字段称为 A 指向 B 的外键,B 也称为 A 的外键表。
这里说的主键是指逻辑上的主键,也就是在表中取值唯一、可以用于唯一确定某条记录的字段,不一定是在数据库表上建立过的主键。
外键表是多对一的关系,且只有 JOIN 和 LEFT JOIN,而 FULL JOIN 非常罕见。
典型例子:商品交易表和商品信息表。
显然,外键关联是不对称的。事实表和维表的位置不能互换。
-
同维表
表 A 的主键与表 B 的主键关联,A 和 B 互称为同维表。同维表是一对一的关系,JOIN、LEFT JOIN 和 FULL JOIN 的情况都会有,不过在大多数数据结构设计方案中,FULL JOIN 也相对少见。
典型例子:员工表和经理表。
同维表之间是对称的,两个表的地位相同。同维表还构成是等价关系,A 和 B 是同维表,B 和 C 是同维表,则 A 和 C 也是同维表。 -
主子表
表 A 的主键与表 B 的部分主键关联,A 称为主表,B 称为子表。主子表是一对多的关系,只有 JOIN 和 LEFT JOIN,不会有 FULL JOIN。
典型例子:订单和订单明细。
主子表也是不对称的,有明确的方向。
在 SQL 的概念体系中并不区分外键表和主子表,多对一和一对多从 SQL 的观点看来只是关联方向不同,本质上是一回事。确实,订单也可以理解成订单明细的外键表。但是,我们在这里要把它们区分开,将来在简化语法和性能优化时将使用不同的手段。
我们说,这三种 JOIN 已经涵盖了绝大多数等值 JOIN 的情况,甚至可以说几乎全部有业务意义的等值 JOIN 都属于这三类,把等值 JOIN 限定在这三种情况之中,几乎不会减少其适应范围。
仔细考察这三种 JOIN,我们发现所有关联都涉及主键,没有多对多的情况,是不是可以不考虑这种情况?
是的!多对多的等值 JOIN 几乎没有业务意义。
如果两个表 JOIN 时的关联字段没有涉及到任何主键,那就会发生多对多的情况,而这种情况几乎一定还会有一个规模更大的表把这两个表作为维表关联起来。比如学生表和科目表在 JOIN 时,会有个成绩表把学生表和科目表作为维表,单纯只有学生表和科目表的 JOIN 没有业务意义。
当写 SQL 语句时发现多对多的情况,那大概率是这个语句写错了!或者数据有问题!这条法则用于排除 JOIN 错误很有效。
不过,我们一直在说“几乎”,并没有用完全肯定的说法,也就是说,多对多在非常罕见的情况下也会业务意义。可举一例,用 SQL 实现矩阵乘法时会发生多对多的等值 JOIN,具体写法读者可以自行补充。
SQL 中笛卡尔积再过滤这种 JOIN 定义,确实非常简单,而简单的内涵将得到更大的外延,可以把多对多等值 JOIN 甚至非等值 JOIN 等都包括进来。但是,过于简单的内涵无法充分体现出最常见等值 JOIN 的运算特征。这会导致编写代码和实现运算时就不能利用这些特征,在运算较为复杂时(涉及关联表较多以及有嵌套的情况),无论是书写还是优化都非常困难。而充分利用这些特征后,我们就能创造出更简单的书写形式并获得更高效的运算性能,后面的内容中将会逐步加以说明。
与其为了把罕见情况也被包括进来而把运算定义为更通用的形式,还不如把这些情况定义成另一种运算更为合理。