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

问题描述

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

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

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

是否可以在 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,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,FOREIGN KEY ("entity_id") REFERENCES "entity" ("id") ON DELETE CASCADE ON UPDATE CASCADE
) ;

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

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

解决方法

没有简单的方法, 您必须在实体表中添加一列来指示实体的类型:

CREATE TYPE type_entity_class AS ENUM ('entity_tag','object','view');

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

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

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

改变:

外键 ("entity_id","entity_class") REFERENCES "entity" ("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,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,view_id)
            values (14,'no-type-14',null);
commit; -- fails!

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

begin transaction;
insert into entity (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 上的运行示例。