将外键关系限制为相关子类的行

时间:2014-07-23 12:45:48

标签: sql postgresql database-design foreign-keys postgresql-9.0

概述:我试图在数据库中表示几种类型的实体,这些实体具有许多共同的基本字段,然后每个实体都有一些其他字段不与其他类型的实体共享。工作流程经常涉及将实体列在一起,所以我决定有一个包含其公共字段的表,然后每个实体都有自己的表及其附加字段。

实施:所有实体都有一个共同的领域,即“状态”;但是,某些实体仅支持所有可能状态的子集。我还希望每种类型的实体都强制使用其状态子集。最后,我还希望在将实体列在一起时包含此字段,因此将其从公共字段集中排除似乎不正确,因为这将需要特定类型表的并集以及SQL中缺少“implements interface”包含该字段将按惯例进行。

为什么我在这里:下面是一个功能正常的解决方案,但我感兴趣的是,有更好或更常见的方法来解决问题。特别是,这个解决方案要求我制作冗余unique约束和冗余状态字段的事实感觉不够优雅。

create schema test;

create table test.statuses(
    id      integer     primary key
);
create table test.entities(
    id      integer     primary key,
    status  integer,
    unique(id, status),
    foreign key (status) references test.statuses(id)
);

create table test.statuses_subset1(
    id      integer     primary key,
    foreign key (id) references test.statuses(id)
);
create table test.entites_subtype(
    id integer primary key,
    status integer,
    foreign key (id) references test.entities(id),
    foreign key (status) references test.statuses_subset1(id),
    foreign key (id, status) references test.entities(id, status) initially deferred
);

一些数据:

insert into test.statuses(id) values
    (1),
    (2),
    (3);
insert into test.entities(id, status) values
    (11, 1),
    (13, 3);
insert into test.statuses_subset1(id) values
    (1), (2);
insert into test.entites_subtype(id, status) values
    (11, 1);

-- Test updating subtype first
update test.entites_subtype
    set status = 2
    where id = 11;
update test.entities
    set status = 2
    where id = 11;

-- Test updating base type first
update test.entities
    set status = 1
    where id = 11;
update test.entites_subtype
    set status = 1
    where id = 11;

/* -- This will fail
insert into test.entites_subtype(id, status) values
    (12, 3);
*/

1 个答案:

答案 0 :(得分:1)

简化fk约束的MATCH SIMPLE行为

如果至少有一列具有默认MATCH SIMPLE行为的多列外部约束为NULL,则不会强制执行约束。您可以在此基础上进一步简化设计。

CREATE SCHEMA test;

CREATE TABLE test.status(
   status_id  integer PRIMARY KEY
  ,sub        bool NOT NULL DEFAULT FALSE  -- TRUE .. *can* be sub-status
  ,UNIQUE (sub, status_id)
);

CREATE TABLE test.entity(
   entity_id  integer PRIMARY KEY
  ,status_id  integer REFERENCES test.status  -- can reference all statuses
  ,sub        bool      -- see examples below
  ,additional_col1 text -- should be NULL for main entities
  ,additional_col2 text -- should be NULL for main entities
  ,FOREIGN KEY (sub, status_id) REFERENCES test.status(sub, status_id)
     MATCH SIMPLE ON UPDATE CASCADE  -- optionally enforce sub-status
);

非常便宜存储一些额外的NULL列(对于主要实体):

BTW,per documentation:

  

如果省略 refcolumn 列表,则会使用 reftable 的主键。

演示数据:

INSERT INTO test.status VALUES
  (1, TRUE)
, (2, TRUE)
, (3, FALSE);     -- not valid for sub-entities

INSERT INTO test.entity(entity_id, status_id, sub) VALUES
  (11, 1, TRUE)   -- sub-entity (can be main, UPDATES to status.sub cascaded)
, (13, 3, FALSE)  -- entity  (cannot be sub,  UPDATES to status.sub cascaded)
, (14, 2, NULL)   -- entity  (can    be sub,  UPDATES to status.sub NOT cascaded)
, (15, 3, NULL)   -- entity  (cannot be sub,  UPDATES to status.sub NOT cascaded)

SQL Fiddle(包括您的测试)。

替代单一FK

另一种选择是将(status_id, sub)的所有组合输入status表(每status_id只能有2个)并且只有一个fk约束:

CREATE TABLE test.status(
   status_id  integer
  ,sub        bool DEFAULT FALSE
  ,PRIMARY KEY (status_id, sub)
);

CREATE TABLE test.entity(
   entity_id  integer PRIMARY KEY
  ,status_id  integer NOT NULL  -- cannot be NULL in this case
  ,sub        bool NOT NULL     -- cannot be NULL in this case
  ,additional_col1 text
  ,additional_col2 text
  ,FOREIGN KEY (status_id, sub) REFERENCES test.status
     MATCH SIMPLE ON UPDATE CASCADE  -- optionally enforce sub-status
);

INSERT INTO test.status VALUES
  (1, TRUE)       -- can be sub ...
  (1, FALSE)      -- ... and main
, (2, TRUE)
, (2, FALSE)
, (3, FALSE);     -- only main

相关答案:

保留所有表格

如果由于某些原因需要所有四个表而不在问题中,请考虑对dba.SE上非常相似的问题的详细解决方案:

Inheritance

......可能是您描述的另一种选择。如果你能和some major limitations一起生活。相关回答: