前两期我们已经做了充足的铺垫,向你描绘了一个强大的数据库——PostgreSQL。通过之前的实战,你应该感受到SQL是一门富有魅力的强大技术。今天,我将为你展示一个属于PostgreSQL独有的特性——表继承,这是我最爱的特性之一,它非常实用。一旦你掌握了这项特性,并把它运用于你的项目中,一定会大呼过瘾的。

想象我们在做一个企业业务管理系统,需要为客户提供各种业务的增删改查功能。比如进销存这样的系统,我们要管理采购入库销售出库等一系列的业务,而这些业务通常会有一些通用的设计,比如制单人制单时间等等。

采用传统数据库的时候,我们通常就会在所有的业务表都添加这些相同的字段,虽然繁琐点,也并非无法忍受。但是当你积攒了成百个这种业务时,如果再想增加个字段,比如增加个修改人修改时间什么的,那场面就不太美妙了。

还有另外一个场景,企业的业务,几乎每个单据(业务),都会有一个叫编号的东西,比如:订单编号、物流单号、支付流水号。这时,如果你的产品经理跟你提个需求,想做一个万能搜索,用户可以在文本框里随便输入一个编号,就能对系统中所有的业务单据编号进行模糊匹配,你打算怎么设计呢?

来看看在拥有表继承特性的PostgreSQL你该怎么做吧。

  1. 创建业务基础表(父表),仅需包含几个关键字段即可:

    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。

  2. 创建业务表A:

    create table a_business
    (
    	a_column varchar
    ) inherits (basic_business);
    

    这里我们通过inherits关键字,声明了a_businessbasic_business的继承关系。因为这种继承关系的存在,basic_business表中已经存在的字段,无需再次创建,我们仅需要在a_business中关注该业务独有的字段即可。

  3. 需要注意一点,表继承关系,可以传递默认值非空约束等,但是主键行为不会传递,所以针对创建成功的a_business表,我们还要给他补一个主键声明:

    alter table a_business
    	add constraint a_business_pk
    		primary key (id);
    
  4. 同样的,我们也准备一个业务B的表b_business,方式跟A一样,就不再赘述了。最后我们获得了如下图展示的三张表:

    71d38eed-c9d9-45a8-a93a-8bd60e8bac4c-1

  5. 下面尝试在a_businessb_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');
    
  6. 我猜你已经迫不及待想去select一下basic_business了吧,那咱们赶紧来查看一下:

    select * from basic_business;
    

    你将会得到如下结果:

    583f6958-3f33-4cf4-9aca-6b3046d8236d

    可以看到我们插入在a_businessb_business中的数据都被查询到了。并且由于basic_business的字段只有三个,所以结果返回的也是这三个字段。

    是不是有点意思呢?更精彩的还在后面。

    当我们尝试对basic_business追加一个字段,比如增加一个i_status用来标记单据状态,并且我还给这个字段指定了一个默认值0

    alter table basic_business
    	add i_status int default 0;
    

    这个时候,当我们回过头来select任意一个业务表的时候,你会惊喜的发现,业务表也会相应的追加字段,并且连默认值都准备就绪了。

    select * from a_business;
    

    你会看到如下结果:

    52b0cf1c-9019-488e-8fd7-dc59f4de11a7

    甚至当你觉得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;
    

    结果如下:

    50d008d0-d035-4286-9237-7cdca87251b2

    怎么样?是不是所有的结果都与我们期望的一样。

    顺便说一下,我们刚才把一个varchar类型改成了text,其实这是一种几乎无意义的操作。在PostgreSQLvarchar就是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"
    

    这我们就明白了,对子表来说,继承而来的字段是不允许修改的。其实这是完全符合直觉的,毕竟如果子表可以二次定义继承字段的话,假设父表也对该字段做了变更,系统就不知道以谁为准了。所以最佳实现方式,就是单向传递,以避免潜在的冲突。

  7. 下面让我们来实现之前描述的第二个业务场景——通过对父表的t_code的模糊查询,同时对多个业务进行数据匹配:

    select * from basic_business where t_code like '%01%';
    

    结果如下:

    24f5315e-4fa1-41da-84ac-a2b2e31cc734

    我们已经读到了分别位于a_businessb_business的两条数据。结果是不是很美妙呢?以后就是有再多的子表都不怕,只要都是继承basic_business的,我们都能查出来客户想要的数据。甚至不需要怎么修改代码。

    你可能会问了,单看上面两行数据,并没有办法明确标记出该行数据具体属于哪个表呢?不着急,PostgreSQL已经替我们想到了,如果想多查询一列,用以显示具体该行数据的事实归属,我们可以这样做:

    select *,TABLEOID::regclass from basic_business where t_code like '%01%';
    

    这样结果就多了一列,如图:

    d7371289-9e6d-42d3-a47e-e78a8bc2a413

    讲到这里你应该明白了,数据事实上永远只有一份,并且只是存在于具体的子表中。之所以查询父表能够看到子表的数据,只是PostgreSQL帮我们做的一点小魔法,这说起来有点像一个union了好多表的视图。

  8. 假设我查询父表的时候,不想带出子表的数据,该怎么做呢?其实也很简单,一个关键字的事:

    select * from only basic_business;
    

    这样你就只会获取到明确insertbasic_business表中的数据了。

总结

表继承功能算不上有很强技术储备的设计,但却异常实用。除了上面演示的抽象业务通用字段,其实还有好多地方可以用上这个特性,比如软删除、跟第三方对接数据(你控制父表,他控制子表)等等,小伙伴们还是开动脑筋自己多多尝试一下吧。
关于PostgreSQL表继承特性,今天就先介绍到这里。我想你应该体会到作为一款关系型数据库,PostgreSQL提供了不少颇具巧思的设计,以方便我们在实际业务场景中使用它。更多PostgreSQL实用特性,欢迎你继续关注我的专栏,咱们下期再见。

推荐阅读本系列的其他文章: