PostgreSQL技术问答36 - INHERITS 表继承本文讨论了Postgres中,表继承(Inherits)的
本文是《PostgreSQL技术问答》系列文章中的一篇。关于这个系列的由来,可以参阅开篇文章:
文章的编号只是一个标识,在系列中没有明确的逻辑顺序和意义。读者进行阅读时,不用太关注这个方面。
本文讨论的内容是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);定义继承表的时候,需要指定其继承关系,并且只需要定义它特有的字段;然后基于数据的特点,决定是否插入到主表或者子表当中。这时数据集合的关系,就是子表中的数据,是主表数据的一个子集,但有扩展的属性。
根据上面的例子,我们可以总结出来一些表继承相关的特点:
- 继承列: 继承表表会继承父表中的所有列,父表中的列在子表中自动存在,并且如果在父表上进行字段的修改,也会自动反映在子表中
- 扩展列: 子表中可以有额外的列,来描述其扩展性
- 数据隔离: 虽然子表继承了父表的结构,但子表和父表之间的数据是分离的。插入到子表中的数据不会自动出现在父表中,反之亦然。
- 查询:在查询时,如果对父表进行查询,默认情况下会包含所有子表的数据;而查询子表,就只有子表的数据
- 限制:这应该是一个方便性的设计,在查询主表时,可以通过ONLY关键字来限制查询只针对父表数据
- 性能优化: 在有多个公共属性的多个数据集中,可以按照数据本身的特性和需求,选择其所在的数据表,这样可以按需存储,避免占用不必要的字段空间
能不能嵌套继承和多次继承
在前面的章节中,相信读者已经大致的理解了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)的相关问题。继承的基本概念来自面向对象的编程,体现在数据库中,可以用于对表结构进行扩展,从而对多个拥有部分相同结构的数据集进行更好的管理和维护。
转载自:https://juejin.cn/post/7409877909999288361