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

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

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

实现:有一个公共(public)字段“status”,所有实体都有;但是,某些实体仅支持所有可能状态的子集。我还希望每种类型的实体强制使用其状态子集。最后,我还想在将实体列出在一起时包含此字段,因此将其从公共(public)字段集中排除似乎是不正确的,因为这需要特定类型表的并集,并且 SQL 中缺少“实现接口(interface)”意味着将该字段包含在内将是按照惯例。

为什么我在这里:下面是一个实用的解决方案,但我很感兴趣是否有更好或更常见的方法来解决问题。特别是,该解决方案要求我创建冗余的唯一约束和冗余状态字段,这让人感觉不太优雅。

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);
*/

最佳答案

简化 MATCH SIMPLE 上的构建fk 约束的行为

如果默认 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 列(对于主要实体)非常便宜:

顺便说一句,per documentation:

If the refcolumn list is omitted, the primary key of the reftable is used.

演示数据:

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 。相关答案:

关于sql - 将外键关系限制为相关子类型的行,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/24910861/

相关文章:

sql - 当参数没有值时,如何将表中的所有值传递到ssrs中?

sql - LEFT JOIN 返回空结果集

java - 如何使用 JDBI 将 2d 数组从 PostgreSQL DB 转换为 java 2d 数组?

sql - 我应该使用 SQL_Variant 数据类型吗?

sql - 基于表列的多个自动增量 ID

sql - 如何使包装器返回 ref cursor 以外的东西

mysql: 值可以是 "null",但不一定是 "specific_value"

SQL Server 存储过程连接字符串作为查询

postgresql - CentOS 7 pg_upgrade 权限错误

database - 多个父对象的注释