PostgreSQL 8.2.3 中文文档
后退快退章5. 数据定义快进前进

5.8. 继承

PostgreSQL 实现了表继承,这个特性对数据库设计人员来说是一个很有效的工具。SQL99 及以后的标准定义了类型继承特性,和我们在这里描述的很多特性有区别。

让我们从一个例子开始:假设我们试图制作一个城市数据模型。每个州都有许多城市,但是只有一个首府。我们希望能够迅速检索任何州的首府。这个任务可以通过创建两个表来实现,一个是州府表,一个是非州府表。不过,如果我们不管什么城市都想查该怎么办?继承的特性可以帮助我们解决这个问题。我们定义 capitals 表,它继承自 cities 表:

CREATE TABLE cities (
    name            text,
    population      float,
    altitude        int     -- 英尺
);

CREATE TABLE capitals (
    state           char(2)
) INHERITS (cities);

在这种情况下,capitals继承它的父表 cities 中的所有属性。州首府有一个额外的 state 属性显示其所在的州。

在 PostgreSQL 里,一个表可以从零个或多个其它表中继承属性,而且一个查询既可以引用一个表中的所有行,也可以引用一个表及其所有后代表的行(后面这个是缺省行为)。比如,下面的查询查找所有海拔 500 英尺以上的城市名,包括州首府:

SELECT name, altitude
    FROM cities
    WHERE altitude > 500;

使用 PostgreSQL 教程里面的数据(参阅节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

通过和 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

对于 INSERTCOPY ,继承并不自动影响其后代表。在我们的例子里,下面的 INSERT 语句将会失败:

INSERT INTO cities (name, population, altitude, state)
VALUES ('New York', NULL, NULL, 'NY');

我们可能希望数据被传递到 capitals 表里面去,但这是不会发生的:INSERT 总是插入明确声明的那个表。在某些情况下,我们可以使用规则进行重定向插入(参阅章35)。不过它不能对上面的例子有什么帮助,因为 cities 表并不包含 state 字段,因此命令在规则施加之前就会被拒绝掉。

所有父表的检查约束和非空约束都会自动被所有子表继承。不过其它类型的约束(唯一、主键、外键)不会被继承。

一个子表可以从多个父表继承,这种情况下它将拥有所有父表字段的总和,并且子表中定义的字段也会加入其中。如果同一个字段名出现在多个父表中,或者同时出现在父表和子表的定义里,那么这些字段就会被"融合",这样在子表里就只有一个这样的字段。要想融合,字段的数据类型必须相同,否则就会抛出一个错误。融合的字段将会拥有其父字段的所有检查约束,并且如果某个父字段存在非空约束,那么融合后的字段也必须是非空的。

表继承通常使用带 INHERITS 子句的 CREATE TABLE 语句定义。另外,一个已经用此方法定义的子表可以使用带 INHERITALTER TABLE 命令添加一个新父表。注意:该子表必须已经包含新父表的所有字段且类型一致,此外新父表的每个约束的名字及其表达式都必须包含在此子表中。同样,一个继承链可以使用带 NO INHERITALTER TABLE 命令从子表上删除。允许动态添加和删除继承链对基于继承关系的表分区(参见节5.9)很有用。

创建一个将要作为子表的新表的便利途径是使用带 LIKE 子句的 CREATE TABLE 命令。它将创建一个与源表字段相同的新表。如果源表中存在约束,那么应该指定 LIKEINCLUDING CONSTRAINTS 选项,因为子表必须包含源表中的 CHECK 约束。

任何存在子表的父表都不能被删除,同样,子表中任何从父表继承的字段也不能被删除或修改。如果你想删除一个表及其所有后代,最简单的办法是使用 CASCADE 选项。

ALTER TABLE 会把所有数据定义和检查约束传播到后代里面去。另外,只有在使用 CASCADE 选项的情况下,才能删除父表的字段或者约束。ALTER TABLE 在重复字段融合和拒绝方面和 CREATE TABLE 的规则相同。

5.8.1. 警告

表访问权限并不会自动继承。因此,要么同时具有访问父表与所有子表的权限,要么必须使用 ONLY 表示法。所以在添加新子表的时候,请注意给它赋予适当的权限。

继承的一个严重局限性是索引(包括唯一约束)和外键约束只能用于单个表,而不能包括它们的子表(不管对引用表还是被引用表都是如此),因此,在上面的例子里:

这些缺点很可能在将来的版本中修补,但同时你也需要考虑一下,继承是否对你的问题真正有用。

【已废弃】在7.1以前的 PostgreSQL 版本里,缺省的行为是不在查询里包含子表。后来发现这么做很容易出错并且也违反了 SQL 标柱。你可以通过关闭 sql_inheritance 配置选项来兼容以前的行为。


后退首页前进
模式上一级分区