PostgreSQL实现了表继承,这个特性对数据库设计人员来说是一个很有效的工具。 SQL99及以后的标准定义了类型继承特性,和我们在这里描述的很多特性有区别。
让我们从一个例子开始:假设我们试图制作一个城市数据模型。每个州都有许多城市,但是只有一个首府。 我们希望能够迅速检索任何州的首府。这个任务可以通过创建两个表来实现,一个是州府表,一个是非州府表。 不过,如果我们不管什么城市都想查该怎么办?继承的特性可以帮助我们解决这个问题。 我们定义capitals 表,它继承自cities表:
CREATE TABLE cities ( name text, population float, altitude int -- in feet ); CREATE TABLE capitals ( state char(2) ) INHERITS (cities);
在这种情况下,capitals表继承它的父表cities中的所有属性。州首府有一个额外的state属性显示其所在的州。
在PostgreSQL里,一个表可以从零个或多个其它表中继承属性,而且一个查询既可以引用一个表中的所有行, 也可以引用一个表及其所有后代表的行(后面这个是缺省行为)。比如,下面的查询查找所有海拔500英尺以上的城市名,包括州首府:
SELECT name, altitude FROM cities WHERE altitude > 500;
使用PostgreSQL教程里面的数据(参阅Section 2.1),它返回:
name | altitude -----------+---------- Las Vegas | 2174 Mariposa | 1953 Madison | 845
另一方面,如果要找出不包括州首府的所有海拔超过500英尺的城市,查询应该是这样的:
SELECT name, altitude FROM ONLY cities WHERE altitude > 500; name | altitude -----------+---------- Las Vegas | 2174 Mariposa | 1953
cities前面的ONLY表明该查询应该只针对cities而不包括其后代。许多我们已经讨论过的命令— SELECT, UPDATE,DELETE —都支持ONLY关键字。
有时候你可能想知道某个行版本来自哪个表。在每个表里我们都有一个 tableoid系统属性可以告诉你源表是谁:
SELECT c.tableoid, c.name, c.altitude FROM cities c WHERE c.altitude > 500;
结果如下(你可能会得到不同的 OID): tableoid | name | altitude ----------+-----------+---------- 139793 | Las Vegas | 2174 139793 | Mariposa | 1953 139798 | Madison | 845 tableoid | name | altitude ----------+-----------+---------- 139793 | Las Vegas | 2174 139793 | Mariposa | 1953 139798 | Madison | 845 通过和pg_class做一个连接,就可以看到实际的表名字:
SELECT p.relname, c.name, c.altitude FROM cities c, pg_class p WHERE c.altitude > 500 AND c.tableoid = p.oid;
它返回:
relname | name | altitude ----------+-----------+---------- cities | Las Vegas | 2174 cities | Mariposa | 1953 capitals | Madison | 845
对于INSERT或COPY,继承并不自动影响其后代表。在我们的例子里,下面的INSERT语句将会失败:
INSERT INTO cities (name, population, altitude, state) VALUES ('New York', NULL, NULL, 'NY');
我们可能希望数据被传递到capitals表里面去,但这是不会发生的:INSERT总是插入明确声明的那个表。 在某些情况下,我们可以使用规则进行重定向插入(参阅Chapter 37)。不过它不能对上面的例子有什么帮助, 因为cities表并不包含state字段,因此命令在规则施加之前就会被拒绝掉。
所有父表的检查约束和非空约束都会自动被所有子表继承。不过其它类型的约束(唯一、主键、外键)不会被继承。
一个子表可以从多个父表继承,这种情况下它将拥有所有父表字段的总和,并且子表中定义的字段也会加入其中。 如果同一个字段名出现在多个父表中,或者同时出现在父表和子表的定义里,那么这些字段就会被"融合", 这样在子表里就只有一个这样的字段。要想融合,字段的数据类型必须相同,否则就会抛出一个错误。融合的字段将会拥有其父字段的所有检查约束, 并且如果某个父字段存在非空约束,那么融合后的字段也必须是非空的。
表继承通常使用带INHERITS子句的CREATE TABLE语句定义。另外,一个已经用此方法定义的子表 可以使用带INHERIT的ALTER TABLE命令添加一个新父表。注意:该子表必须已经包含新父表的所有字段且类型一致, 此外新父表的每个约束的名字及其表达式都必须包含在此子表中。同样,一个继承链可以使用带NO INHERIT的ALTER TABLE命令从子表上删除。 允许动态添加和删除继承链对基于继承关系的表分区(参见Section 5.9)很有用。
创建一个将要作为子表的新表的便利途径是使用带LIKE子句的CREATE TABLE命令。 它将创建一个与源表字段相同的新表。如果源表中存在约束,那么应该声明LIKE的INCLUDING CONSTRAINTS选项, 因为子表必须包含源表中的CHECK约束。
任何存在子表的父表都不能被删除,同样,子表中任何从父表继承的字段也不能被删除或修改。如果你想删除一个表及其所有后代, 最简单的办法是使用CASCADE选项。
ALTER TABLE会把所有数据定义和检查约束传播到后代里面去。另外,只有在使用CASCADE选项的情况下,才能删除父表的字段或者约束。 ALTER TABLE在重复字段融合和拒绝方面和CREATE TABLE的规则相同。
注意表的访问权限是如何处理的。查询父表可以自动访问子表的数据而不用进一步检查访问权限。 这个保留数据(也)在父表中的外貌。然而,直接访问子表不会自动被允许,而且需要要求被授予进一步的权限。
注意,不是所有的SQL命令可以在所有的继承层次上正常工作。数据查询,数据修改,模式修改的命令(比如,SELECT,UPDATE,DELETE, ALTER TABLE的大多数变型, 但不是INSERT和ALTER TABLE ...RENAME) 典型的默认包括子表和支持ONLY符号来排除它们。 为数据库维护和调优的命令(例如,REINDEX,VACUUM)通常只对个别工作, 物理表格不支持递归超过继承层次结构。每个命令的各自行为都被记录在参考部分(Reference I, SQL命令)。
继承的一个严重局限性是索引(包括唯一约束)和外键约束只能用于单个表,而不能包括它们的子表 (不管对引用表还是被引用表都是如此),因此,在上面的例子里:
即使我们声明cities.name为UNIQUE或PRIMARY KEY也不会阻止capitals表拥有重复名字的cities数据行。 并且这些重复的行在查询cities表的时候会显示出来。实际上,缺省时capitals将完全没有唯一约束, 因此可能包含带有同名的多个行。你应该给capitals增加唯一约束,但即使这样做也不能避免与cities的重复。
类似的,即使我们声明cities.name参照REFERENCES某些其它的表,这个约束也不会自动传播到capitals表。在这种条件下, 你可以通过手工给capitals表增加同样的REFERENCES约束来做到这点。
声明一个其它表的字段为REFERENCES cities(name)将允许其它表包含城市名,但是不包含首府名。这种情况下没有很好的绕开办法。
这些缺点很可能在将来的版本中修补,但同时你也需要考虑一下,继承是否对你的问题真正有用。
已废弃: 在7.1以前的PostgreSQL版本里,缺省的行为是不在查询里包含子表。后来发现这么做很容易出错并且也违反了SQL标柱。 你可以通过关闭sql_inheritance配置选项来兼容以前的行为。