确保一组表中只有一个外键引用

Ensure there's exactly one foreign key reference from a set of tables

我正在尝试设计一种数据库结构,允许我将公共字段提取到一个名为“实体”的 table 中。

您可以将“实体”视为抽象的class。每个“实体”都有一个所有者(一个引用的用户帐户)、一个创建时间和一些键值标签。

每个“实体”实际上要么是“对象”,要么是“视图”。从来没有。从来没有 none 个。

是否可以在 PostgreSQL 数据库(最新版本)上实施此约束? 如果没有,请告诉我并随时提出对我当前架构的更改建议。

我的初始化 SQL 看起来像这样:

CREATE TABLE "account" (
    "id" BIGSERIAL NOT NULL,
    "issuer" VARCHAR NOT NULL,
    "name" VARCHAR NOT NULL,
    PRIMARY KEY ("id"),
    UNIQUE ("issuer", "name")
) ;

CREATE TABLE "entity" (
    "id" BIGSERIAL NOT NULL,
    "owner_account_id" BIGINT NOT NULL,
    "creation_time" TIMESTAMP NOT NULL,
    PRIMARY KEY ("id"),
    FOREIGN KEY ("owner_account_id") REFERENCES "account" ("id") ON DELETE CASCADE ON UPDATE CASCADE
) ;

CREATE TABLE "entity_tag" (
    "entity_id" BIGINT NOT NULL,
    "key" VARCHAR(100) NOT NULL,
    "value" VARCHAR(1000) NOT NULL,
    PRIMARY KEY ("entity_id", "key"),
    FOREIGN KEY ("entity_id") REFERENCES "entity" ("id") ON DELETE CASCADE ON UPDATE CASCADE
) ;

CREATE TABLE "object" (
    "id" BIGSERIAL NOT NULL,
    "entity_id" BIGINT NOT NULL,
    "mime_type" VARCHAR NOT NULL,
    "size" BIGINT NOT NULL,
    PRIMARY KEY ("id"),
    FOREIGN KEY ("entity_id") REFERENCES "entity" ("id") ON DELETE CASCADE ON UPDATE CASCADE
) ;

CREATE TABLE "view" (
    "id" BIGSERIAL NOT NULL,
    "entity_id" BIGINT NOT NULL,
    "view_expression" JSON NOT NULL,
    PRIMARY KEY ("id"),
    FOREIGN KEY ("entity_id") REFERENCES "entity" ("id") ON DELETE CASCADE ON UPDATE CASCADE
) ;

(当然,我可以而且我目前确实在我的应用程序中强制执行此操作。但是如果也有一种方法可以在数据库上强制执行此操作,我想这样做)

没有简单的方法, 你必须在实体table:

中添加一列来表示实体的类型

创建类型 type_entity_class 作为枚举 ('entity_tag', 'object', 'view');

在实体中添加一列 table: entity_class type_entity_class,

在entity_tagtable中添加列: entity_class type_entity_class 检查 (entity_class = 'entity_tag')

在对象 table 中添加列: entity_class type_entity_class 检查 (entity_class = 'object') ..

变化:

外键(“entity_id”,“entity_class”)引用“实体”(“id”,“entity_class”)

您可以在 PostgreSQL(PostgreSQL 多么棒)和 Oracle 中执行此操作,因为您需要一个支持可延迟约束的引擎, SQL 标准的组成部分。

你可以这样做:

create table entity (
  id int primary key not null,
  name varchar(20) not null,
  object_id int,
  view_id int,
  check (object_id is null and view_id is not null
      or object_id is not null and view_id is null)
);

create table object (
  id int primary key not null references entity (id),
  mime_type varchar(20) not null
);

create table view (
  id int primary key not null references entity (id),
  view_expression varchar(50) not null
);

alter table entity add constraint c1
foreign key (object_id) references object (id) deferrable initially deferred;

alter table entity add constraint c2
foreign key (view_id) references view (id) deferrable initially deferred;

现在,您可以插入一个对象和一个视图:

begin transaction;
insert into entity (id, name, object_id, view_id)
            values (10, 'Object-10', 10, null);
insert into object (id, mime_type) values (10, 'image/png');
commit;

begin transaction;
insert into entity (id, name, object_id, view_id) 
            values (12, 'View-12', null, 12);
insert into view (id, view_expression) values (12, 'a+b*c');
commit;

但是不能插入抽象实体(没有具体行):

begin transaction;
insert into entity (id, name, object_id, view_id)
            values (14, 'no-type-14', null, null);
commit; -- fails!

您也不能插入既是对象又是视图的实体:

begin transaction;
insert into entity (id, name, object_id, view_id) values (16, 'Dual-16', 16, 16);
insert into object (id, mime_type) values (16, 'text/plain');
insert into view (id, view_expression) values (16, 'x*x');
commit; -- fails!

请记住,行的插入需要包含在事务中以将约束检查推迟到事务结束。

请参阅 DB Fiddle 中的 运行 示例。