前两期我们已经做了充足的铺垫,向你描绘了一个强大的数据库——PostgreSQL
。通过之前的实战,你应该感受到SQL
是一门富有魅力的强大技术。今天,我将为你展示一个属于PostgreSQL
独有的特性——表继承,这是我最爱的特性之一,它非常实用。一旦你掌握了这项特性,并把它运用于你的项目中,一定会大呼过瘾的。
想象我们在做一个企业业务管理系统,需要为客户提供各种业务的增删改查功能。比如进销存这样的系统,我们要管理采购
、入库
、销售
、出库
等一系列的业务,而这些业务通常会有一些通用的设计,比如制单人
、制单时间
等等。
采用传统数据库的时候,我们通常就会在所有的业务表都添加这些相同的字段,虽然繁琐点,也并非无法忍受。但是当你积攒了成百个这种业务时,如果再想增加个字段,比如增加个修改人
、修改时间
什么的,那场面就不太美妙了。
还有另外一个场景,企业的业务,几乎每个单据(业务),都会有一个叫编号
的东西,比如:订单编号、物流单号、支付流水号。这时,如果你的产品经理跟你提个需求,想做一个万能搜索,用户可以在文本框里随便输入一个编号,就能对系统中所有的业务单据编号进行模糊匹配,你打算怎么设计呢?
来看看在拥有表继承
特性的PostgreSQL
你该怎么做吧。
创建业务基础表(父表),仅需包含几个关键字段即可:
create table basic_business
(
id varchar default gen_random_uuid()
constraint basic_business_pk
primary key,
v_code varchar,
t_create timestamp default now()
);复制为了演示方便,这里我只准备三个字段,分别是:主键(uuid)、编码、创建时间。
也许你注意到了
varchar
类型我们并没有指定长度,这又是一个PostgreSQL
的精彩特性,varchar
类型的长度是可选的,并且长度仅仅是一种约束,而不会对性能产生丝毫影响。所以大胆地采用无长度限制能够在不牺牲性能的情况下帮我们免去很多烦恼——我猜你肯定经历过录入数据超长引起的bug。创建业务表A:
create table a_business
(
a_column varchar
) inherits (basic_business);复制这里我们通过
inherits
关键字,声明了a_business
对basic_business
的继承关系。因为这种继承关系的存在,basic_business
表中已经存在的字段,无需再次创建,我们仅需要在a_business
中关注该业务独有的字段即可。需要注意一点,表继承关系,可以传递
默认值
、非空约束
等,但是主键行为不会传递,所以针对创建成功的a_business
表,我们还要给他补一个主键声明:alter table a_business
add constraint a_business_pk
primary key (id);复制同样的,我们也准备一个业务B的表
b_business
,方式跟A一样,就不再赘述了。最后我们获得了如下图展示的三张表:
下面尝试在
a_business
、b_business
两个表中分别插入一些数据:insert into a_business (v_code, a_column) values ('A0001','test_1');
insert into a_business (v_code, a_column) values ('A0002','test_2');
insert into a_business (v_code, a_column) values ('A0003','test_3');
insert into b_business (v_code, b_column) values ('B0001','test_1');
insert into b_business (v_code, b_column) values ('B0002','test_2');
insert into b_business (v_code, b_column) values ('B0003','test_3');复制我猜你已经迫不及待想去
select
一下basic_business
了吧,那咱们赶紧来查看一下:select * from basic_business;
复制你将会得到如下结果:

可以看到我们插入在
a_business
及b_business
中的数据都被查询到了。并且由于basic_business
的字段只有三个,所以结果返回的也是这三个字段。是不是有点意思呢?更精彩的还在后面。
当我们尝试对
basic_business
追加一个字段,比如增加一个i_status
用来标记单据状态,并且我还给这个字段指定了一个默认值0
。alter table basic_business
add i_status int default 0;复制这个时候,当我们回过头来
select
任意一个业务表的时候,你会惊喜的发现,业务表也会相应的追加字段,并且连默认值都准备就绪了。select * from a_business;
复制你会看到如下结果:

甚至当你觉得
basic_business
字段需要调整一下,重命名或者是改个类型,也都没有关系,比如我们可以这样:alter table basic_business rename column v_code to t_code;
alter table basic_business alter column t_code type text using t_code::text;复制这个时候,我们再查询下
b_business
看看效果select * from b_business;
复制结果如下:

怎么样?是不是所有的结果都与我们期望的一样。
顺便说一下,我们刚才把一个
varchar
类型改成了text
,其实这是一种几乎无意义的操作。在PostgreSQL
中varchar
就是text
另一种叫法。他俩唯一的区别就是varchar
可以声明长度限制,仅此而已。爱动脑筋的你,是不是会问这么一个问题,如果我们在子表,也就是
a_business
中修改一个父表带过来的字段会怎么样呢?咱们就来试试看:alter table a_business alter column t_code type varchar using t_code::varchar
复制当我们打算把
a_business
表中的t_code
类型从text
改为varchar
时,PostgreSQL
给出了一个报错:ERROR: cannot alter inherited column "t_code"
复制这我们就明白了,对子表来说,继承而来的字段是不允许修改的。其实这是完全符合直觉的,毕竟如果子表可以二次定义继承字段的话,假设父表也对该字段做了变更,系统就不知道以谁为准了。所以最佳实现方式,就是单向传递,以避免潜在的冲突。
下面让我们来实现之前描述的第二个业务场景——通过对父表的
t_code
的模糊查询,同时对多个业务进行数据匹配:select * from basic_business where t_code like '%01%';
复制结果如下:

我们已经读到了分别位于
a_business
和b_business
的两条数据。结果是不是很美妙呢?以后就是有再多的子表都不怕,只要都是继承basic_business
的,我们都能查出来客户想要的数据。甚至不需要怎么修改代码。你可能会问了,单看上面两行数据,并没有办法明确标记出该行数据具体属于哪个表呢?不着急,
PostgreSQL
已经替我们想到了,如果想多查询一列,用以显示具体该行数据的事实归属,我们可以这样做:select *,TABLEOID::regclass from basic_business where t_code like '%01%';
复制这样结果就多了一列,如图:

讲到这里你应该明白了,数据事实上永远只有一份,并且只是存在于具体的子表中。之所以查询父表能够看到子表的数据,只是
PostgreSQL
帮我们做的一点小魔法,这说起来有点像一个union
了好多表的视图。假设我查询父表的时候,不想带出子表的数据,该怎么做呢?其实也很简单,一个关键字的事:
select * from only basic_business;
复制这样你就只会获取到明确
insert
到basic_business
表中的数据了。
总结
表继承功能算不上有很强技术储备的设计,但却异常实用。除了上面演示的抽象业务通用字段,其实还有好多地方可以用上这个特性,比如软删除、跟第三方对接数据(你控制父表,他控制子表)等等,小伙伴们还是开动脑筋自己多多尝试一下吧。
关于PostgreSQL
表继承特性,今天就先介绍到这里。我想你应该体会到作为一款关系型数据库,PostgreSQL
提供了不少颇具巧思的设计,以方便我们在实际业务场景中使用它。更多PostgreSQL
实用特性,欢迎你继续关注我的专栏,咱们下期再见。