- 作者:老汪软件技巧
- 发表时间:2024-09-04 04:02
- 浏览量:
本文是《PostgreSQL技术问答》系列文章中的一篇。关于这个系列的由来,可以参阅开篇文章:
《PostgreSQL技术问答00 - Why Postgres》
文章的编号只是一个标识,在系列中没有明确的逻辑顺序和意义。读者进行阅读时,不用太关注这个方面。
本文讨论的内容是PostgreSQL提供的一种简化数据管理的方式: Inherites,表继承。
什么是表继承
Inherits,继承,是一个软件工程和面向对象编程(OOP)的一个核心和重要的概念。编程中的继承允许一个类(子类)继承另一个类(父类或基类)的属性和方法。通过继承,子类可以重用父类的代码,并且可以添加或覆盖父类的行为。继承的主要目的主要是:
在Postgres中,为了解决和优化一些在现实世界中的业务需求,模仿了对象编程中的继承概念,提出和实现了表继承的特性。在某些情况下,可以简化数据库的设计和操作。当然,虽然是模仿和借鉴了对象编程继承的概念,但在数据库实现中,由于主要针对表和数据结构的描述,它们还是有一些区别的(例如缺乏方法继承、重写等)。
下面我们通过一个例子,来了解这些细节问题。
举例说明
-- 主表, 城市
CREATE TABLE city ( id serial PRIMARY KEY, name text );
-- 继承表, 首都
CREATE TABLE capital ( nation text ) INHERITS (city);
-- 插入主表数据
insert into city (name) values ('上海'),('成都'),('芝加哥'), ('马赛');
-- 插入继承表数据
insert into capital (nation,name) values
('中国','北京'),('美国','华盛顿'),('法国','巴黎'), ('德国','柏林');
-- 查询主表数据
defaultdb=> select * from city;
id | name
----+--------
1 | 上海
2 | 成都
3 | 芝加哥
4 | 马赛
5 | 北京
6 | 华盛顿
7 | 巴黎
8 | 柏林
(8 rows)
-- 查询继承表数据
defaultdb=> select * from capital;
id | name | nation
----+--------+--------
5 | 北京 | 中国
6 | 华盛顿 | 美国
7 | 巴黎 | 法国
8 | 柏林 | 德国
(4 rows)
-- 联合查询
defaultdb=> select C.*,P.nation from city C left join capital P on P.id = C.id;
id | name | nation
----+--------+--------
1 | 上海 |
2 | 成都 |
3 | 芝加哥 |
4 | 马赛 |
5 | 北京 | 中国
6 | 华盛顿 | 美国
7 | 巴黎 | 法国
8 | 柏林 | 德国
(8 rows)
-- 限制查询
defaultdb=> select * from only city ;
id | name
----+--------
1 | 上海
2 | 成都
3 | 芝加哥
4 | 马赛
(4 rows)
在上面这个例子中,我们先用一个很常规的方式,定义了一个主表(city);然后基于主表,定义了一个继承表(capital);定义继承表的时候,需要指定其继承关系,并且只需要定义它特有的字段;然后基于数据的特点,决定是否插入到主表或者子表当中。这时数据集合的关系,就是子表中的数据,是主表数据的一个子集,但有扩展的属性。
根据上面的例子,我们可以总结出来一些表继承相关的特点:
能不能嵌套继承和多次继承
在前面的章节中,相信读者已经大致的理解了Postgres中的表继承的基本概念和实现方式。这里我们稍微扩展一下,来看看这个表继承是否支持一些更加灵活的应用方式,比如嵌套继承和多次继承。
嵌套继承,就是将继承表作为主表,再次进行继承。测试示例如下:
-- 创建继承表的继承表
defaultdb=> CREATE TABLE capital2 ( score int ) INHERITS (capital);
CREATE TABLE
-- 插入数据
defaultdb=> insert into capital2 ( name, nation, score) values ('东京','日本', 90);
INSERT 0 1
-- 查询数据
defaultdb=> select * from city ;
id | name | population
----+--------+------------
1 | 上海 | 0
2 | 成都 | 0
3 | 芝加哥 | 0
4 | 马赛 | 0
5 | 北京 | 0
6 | 华盛顿 | 0
7 | 巴黎 | 0
8 | 柏林 | 0
13 | 东京 | 0
(9 rows)
defaultdb=> select * from capitals ;
ERROR: relation "capitals" does not exist
LINE 1: select * from capitals ;
^
defaultdb=> select * from capital ;
id | name | nation | population
----+--------+--------+------------
5 | 北京 | 中国 | 0
6 | 华盛顿 | 美国 | 0
7 | 巴黎 | 法国 | 0
8 | 柏林 | 德国 | 0
13 | 东京 | 日本 | 0
(5 rows)
defaultdb=> select * from capital2 ;
id | name | nation | population | score
----+------+--------+------------+-------
13 | 东京 | 日本 | 0 | 90
(1 row)
看起来继承嵌套是可以的。但显然,在进行实际操作中,要注意避免额外的复杂性和逻辑冲突。
多种继承,就是基于同一个主表,继承多个子表的情况。下面是实验代码:
defaultdb=> CREATE TABLE smallcity ( nation text, location text ) INHERITS (city);
CREATE TABLE
defaultdb=> insert into smallcity ( name, nation) values ('丽江','云南');
INSERT 0 1
defaultdb=> select * from city;
id | name | population
----+--------+------------
1 | 上海 | 0
2 | 成都 | 0
3 | 芝加哥 | 0
4 | 马赛 | 0
5 | 北京 | 0
6 | 华盛顿 | 0
7 | 巴黎 | 0
8 | 柏林 | 0
14 | 丽江 | 0
13 | 东京 | 0
(10 rows)
defaultdb=> select * from smallcity;
id | name | population | nation | location
----+------+------------+--------+----------
14 | 丽江 | 0 | 云南 |
(1 row)
看起来也是可以的。笔者感觉,Postgres中的表继承,在数据表之间的隔离是做的比较好的。父表和子表相对独立,只是在做实际操作的时候,再对数据进行逻辑的组合。
有什么使用场景和问题?
经过查阅技术材料,在使用表继承的时候,需要了解它有一些应用方面的限制:
Postgres提供的表继承的功能特性,可以在数据结构层面上实现不同数据集合之间的共性和特性关系,为一些数据结构和业务设计提供了一定的灵活性,如果设计使用得当,可以简化数据库设计、数据结构的维护和数据操作。它比较适合于描述包含关系的数据集之间的关系。
在很多种情况下,我们可以通过简单的属性表示,就可以将数据集在一个表中进行分类和标识,以实现类似继承和扩展的效果。当然,理论上而言,使用继承,可以减少数据对磁盘的占用(父表中不需要存储额外的数据字段)。
当然,对于有继承关系的数据库设计,本身会带来一些额外的设计和维护方面的复杂性,并且对于应用开发和数据操作,也提出了更高的要求。
笔者的理解,虽然继承这个特性的出发和想法很好,但考虑到关系数据库本身就有很强的数据关系描述的能力,就显得并不是特别必要,还会增加一些复杂性和逻辑干涉的风险,可能得不偿失。加之,现代化的应用和数据库设计风格,力求简单直接明了。 所以,好像在实际应用场景中,没有看到广泛的讨论和应用,也没有看到Postgres高调的展示和宣传这个特性。
小结
本文讨论了Postgres中,表继承(Inherits)的相关问题。继承的基本概念来自面向对象的编程,体现在数据库中,可以用于对表结构进行扩展,从而对多个拥有部分相同结构的数据集进行更好的管理和维护。