在Postgres中使用可选FK约束的最佳方法是什么?

时间:2019-06-10 14:19:47

标签: postgresql foreign-keys optional

我在Postgres中有一个表,其中包含 Things 。这些事物中的每一种都可以是3种类型之一:

  1. 是一个自包含的事物
  2. SuperThingA
  3. 的一个实例
  4. SuperThingB 的一个实例。

本质上,从对象的角度来说,有一个AbstractThing,扩展为     SuperThingA或SuperThingB的不同方式以及所有记录     Thing表在2个中的1个中未扩展或扩展     方式。

为了在Thing表上表示这一点,我具有许多所有类型的Things共有的字段,并且在SuperThingA和SuperThingB表中有2个可选的FK参考列。

理想情况下,我希望对两者分别使用“ FK IF NOT NULL”约束,但这似乎是不可能的。据我所知,我所能做的就是使两个字段都可为空,并依靠使用代码来维护FK关系,这很麻烦。根据{{​​3}},这似乎在其他数据库(例如SQL Server)中是可行的,但到目前为止,我在PG中找不到的任何方式

我还能如何处理-插入/更新触发器,当这些字段中的任何一个都不为null时检查该值并检查在任何父表中是否存在该值?这对于在Thing表上插入有限的小型父表可能是可行的(公平地说,在这里主要是这种情况-每个父表中最多只有几百条记录,而Thing上的插入量很少表),但在更一般的情况下,如果一个或两个父表都很大,则会在插入时出现性能黑洞

当前未通过FK关系强制实施。我已经查看了PG文档,并且似乎可以确定我没有可选的FK关系(这是可以理解的)。它给我留下了一个类似这样的表定义:


CREATE TABLE IF NOT EXISTS Thing(
    Thing_id             int4           NOT NULL,
    Thing_description    varchar(40),
    Thing_SuperA_FK     int4,
    Thing_SuperB_FK         char(10),
    CONSTRAINT ThingPK PRIMARY KEY (Thing_id)
)
;

2 个答案:

答案 0 :(得分:1)

可空列上的每个外键仅在值非空时强制执行。这是默认行为。

create table a (
    id int4 not null primary key
);

create table b (
    id int4 not null primary key, 
    a_id int4 references a(id)
);

在示例中,表b具有对表a的可选引用。

insert into a values(1); -- entry in table a
insert into b values (1, 1); -- entry in b with reference to a
insert into b values (2, null); -- entry in b with no reference to a

根据您的用例,反转表结构也很有意义。与其让公用表指向另外两个专用表,不如让它反过来。这样,您可以完全避免使用非null列。

create table common(
    id int4 primary key
    -- all the common fields
);

create table special1(
    common_id int4 not null references common(id)
    -- all the special fields of type special1
);

create table special2(
    common_id int4 not null references common(id)
    -- all the special fields of type special2
);

答案 1 :(得分:0)

您需要将SuperN_FK字段定义为可为空的外键,然后您需要在表上使用检查约束来强制执行可选的NULLability要求。

CREATE TABLE Things
   (  ID int primary key
    , col1 varchar(1)
    , col2 varchar(1)

    , SuperA_FK int constraint fk_SuperA references Things(ID)
    , cola1 varchar(1)
    , constraint is_SuperA check ((SuperA_FK is null and cola1 is null) or 
                                  (SuperA_FK is not null and cola1 is not null))

    , SuperB_FK int constraint fk_SuperB references Things(ID)
    , colb1 varchar(1)
    , constraint is_SuberB check ((SuperB_FK is null and colb1 is null) or
                                  (SuperB_FK is not null))

    , constraint Super_Constraint check (
        case when SuperA_FK is not null then 1 else 0 end +
        case when SuperB_FK is not null then 1 else 0 end
        <= 1 )
   );

在上面的示例中,我将检查约束进行了拆分,以简化维护。这两个is_SuperN约束对FK及其相关的明细列强制执行NULL要求,要么全部为NULL,要么FK不为空,并且某些或所有明细列都不为空。最后的Super_Constraint确保最多一个SuperN_FK不为null。