"polymorphism" 用于 FOREIGN KEY 约束

"polymorphism" for FOREIGN KEY constraints

table中有这个字段:

room_id INT NOT NULL CONSTRAINT room_id_ref_room REFERENCES room

我有 3 个 2 table 用于两种房间:standard_roomfamily_room

如何做这样的事情:

room_id INT NOT NULL CONSTRAINT room_id_ref_room REFERENCES standard_room or family_room

我的意思是,room_id 应该参考 standard_room family_room
可以这样做吗?

这是我一直在使用的模式。

CREATE TABLE room (
    room_id serial primary key,
    room_type VARCHAR not null,

    CHECK CONSTRAINT room_type in ("standard_room","family_room"),
    UNIQUE (room_id, room_type)
);

CREATE_TABLE standard_room (
    room_id integer primary key,
    room_type VARCHAR not null default "standard_room",

    FOREIGN KEY (room_id, room_type) REFERENCES room (room_id, room_type),
    CHECK CONSTRAINT room_type  = "standard_room"
);
CREATE_TABLE family_room (
    room_id integer primary key,
    room_type VARCHAR not null default "family_room",

    FOREIGN KEY (room_id, room_type) REFERENCES room (room_id, room_type),
    CHECK CONSTRAINT room_type  = "family_room"
);

也就是说,'subclasses' 指向 super-class,通过类型描述符列(这样指向基 class 的类型是正确的,并且超级 class 的主键与子 classes 相同。

这里是适用于 PostGres 12.8 的已接受答案中的相同 SQL。有一些问题不仅是 CREATE_TABLE 语法错误:

CREATE TABLE room (
    room_id serial primary key,
    room_type VARCHAR not null,

    CONSTRAINT room_in_scope CHECK (room_type in ('standard_room','family_room')),
    CONSTRAINT unique_room_type_combo UNIQUE (room_id, room_type)
);
    
CREATE TABLE standard_room (
    room_id integer primary key,
    room_type VARCHAR not null default 'standard_room',
    
    CONSTRAINT roomid_std_roomtype_fk FOREIGN KEY (room_id, room_type) REFERENCES public."room" (room_id, room_type),
    CONSTRAINT std_room_constraint CHECK (room_type = 'standard_room')
);

CREATE TABLE family_room (
    room_id integer primary key,
    room_type VARCHAR not null default 'family_room',

    CONSTRAINT roomid_fam_roomtype_fk FOREIGN KEY (room_id, room_type) REFERENCES "room" (room_id, room_type),
    CONSTRAINT fam_room_constraint CHECK (room_type  = 'family_room')
);

注意: 上面的 SQL 使用约束强制 child room_type 值默认为 parent tables' room_type 值:'standard_room' 或 'family_room'.

问题: 由于 child tables 主键需要标准房和家庭房主键,这意味着您不能插入超过在这两 child table 中有一个记录。

insert into room (room_type) VALUES ('standard_room');   //Works
insert into room (room_type) values ('family_room');   //Works

insert into standard_room (room_id,pictureAttachment) VALUES (1,'Before Paint');   //Works
insert into standard_room (room_id,pictureAttachment) VALUES (1,'After Paint');   //Fails
insert into standard_room (room_id,pictureAttachment) VALUES (1,'With Furniture');

insert into family_room (room_id,pictureAttachment) VALUES (2, 'Beofre Kids');   //Works
insert into family_room (room_id,pictureAttachment) VALUES (2,'With Kids');   //Fails

要使 table 接受 > 1 行,您必须从 'standard_room' 和 'family_room' table 中删除主键,这是糟糕的数据库设计。 尽管有 26 票赞成,但我会就此向 OP 发送请求,因为我可以看到答案是徒手输入的。


替代解决方案

对于变体较少的小型 tables,一个简单的替代方案是单个 table 和用于不同 table 主键字段的 Bool 列。

单人Table“房间”

Id IsStandardRoom IsFamilyRoom Desc Dimensions
1 True False Double Bed, BIR 3 x 4
2 False True 3 Set Lounge 5.5 x 7
SELECT * FROM Room WHERE IsStdRoom = true;

归根结底,在关系数据库中,当涉及使用 DDL 命令(CREATE、ALTER、DROP)创建必要的相关数据库 table 时,添加房间类型并不常见。

一个典型的未来证明数据库设计允许更多Tables看起来像这样:

多Many-To-ManyTable“房间”

Id TableName TableId
1 Std 8544
2 Fam 236
3 Std 4351

标准版或家庭版:

select * from standard_room sr where sr.room_id in 
(select TableId from room where TableName = 'Std');

select * from family_room fr where  fr.room_id in 
(select id from room where TableName = 'Fam');

或两者兼而有之:

select * from standard_room sr where sr.room_id in 
(select TableId from room where TableName = 'Std')
UNION
select * from family_room fr where  fr.room_id in 
(select id from room where TableName = 'Fam');

Sample SQL to demo Polymorphic fields:

如果你想在多态外键字段中有不同的数据类型,那么你可以使用这个解决方案。 Table r1 存储一个 TEXT 列,r2 存储一个 TEXT[] 数组列,r3 存储一个 POLYGON 列:

CREATE OR REPLACE FUNCTION null_zero(anyelement)
RETURNS INTEGER
LANGUAGE SQL
AS $$
    SELECT CASE WHEN  IS NULL THEN 0 ELSE 1 END;
$$;

CREATE TABLE r1 (
  r1_id SERIAL PRIMARY KEY
, r1_text TEXT
);

INSERT INTO r1 (r1_text)
VALUES ('foo bar');      --TEXT

CREATE TABLE r2 (
  r2_id SERIAL PRIMARY KEY
, r2_text_array TEXT[]
);

INSERT INTO r2 (r2_text_array)
VALUES ('{"baz","blurf"}');     --TEXT[] ARRAY

CREATE TABLE r3 (
  r3_id SERIAL PRIMARY KEY
, r3_poly POLYGON
);
INSERT INTO r3 (r3_poly)
VALUES ( '((1,2),(3,4),(5,6),(7,8))' );     --POLYGON

CREATE TABLE flex_key_shadow (
  flex_key_shadow_id SERIAL PRIMARY KEY
, r1_id INTEGER REFERENCES r1(r1_id)
, r2_id INTEGER REFERENCES r2(r2_id)
, r3_id INTEGER REFERENCES r3(r3_id)
);

ALTER TABLE flex_key_shadow ADD CONSTRAINT only_one_r
    CHECK(
      null_zero(r1_id)
    + null_zero(r2_id)
    + null_zero(r3_id)
    = 1)
;


CREATE VIEW flex_key AS
SELECT
  flex_key_shadow_id as Id
, CASE
  WHEN r1_id IS NOT NULL THEN 'r1'
  WHEN r2_id IS NOT NULL THEN 'r2'
  WHEN r3_id IS NOT NULL THEN 'r3'
  ELSE 'wtf?!?'
  END AS "TableName"
, CASE
  WHEN r1_id IS NOT NULL THEN r1_id
  WHEN r2_id IS NOT NULL THEN r2_id
  WHEN r3_id IS NOT NULL THEN r3_id
  ELSE NULL
  END AS "TableId"
FROM flex_key_shadow
;


INSERT INTO public.flex_key_shadow (r1_id,r2_id,r3_id) VALUES
     (1,NULL,NULL),
     (NULL,1,NULL),
     (NULL,NULL,1);

SELECT * FROM flex_key;