"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_room
和 family_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;
table中有这个字段:
room_id INT NOT NULL CONSTRAINT room_id_ref_room REFERENCES room
我有 3 个 2 table 用于两种房间:standard_room
和 family_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;