我有一张桌子person
和两张桌子foo
和bar
两个都用person
和外键来引用person_id
。我需要创建一个耦合表,将一个foo
链接到一个bar
,但两者都需要引用相同的person
。
如何使用声明性构造在关系结构中表达这一点?或者我是否需要创建一个触发器来强制执行此操作?
CREATE TABLE person
(id int primary key, name text);
INSERT INTO person
(id, name)
VALUES
(1, 'John'),
(2, 'Jane');
CREATE TABLE foo
(id int primary key, person_id int references person(id) not null, comment text);
INSERT INTO foo
(id, person_id, comment)
VALUES
(1, 1, 'John is great'),
(2, 2, 'Jane is great');
CREATE TABLE bar
(id int primary key, person_id int references person(id) not null, comment text);
INSERT INTO bar
(id, person_id, comment)
VALUES
(1, 1, 'John is super great'),
(2, 2, 'Jane is super great');
CREATE TABLE foo_bar
(id int primary key, foo_id int references foo(id), bar_id int references bar(id));
INSERT INTO foo_bar
(id, foo_id, bar_id)
VALUES
(1, 1, 1),
(2, 1, 2), -- Shouldn't be possible!
(3, 2, 1), -- Shouldn't be possible!
(4, 2, 2);
正如此查询所示,在foo_bar
中的行引用John和Jane的数据时,完全可以获得结果:
select foo.comment, bar.comment from foo_bar
inner join foo ON foo.id = foo_bar.foo_id
inner join bar ON bar.id = foo_bar.bar_id;
结果:
John is great, John is super great
John is great, Jane is super great
Jane is great, John is super great
Jane is great, Jane is super great
您可以在包含foo
和bar
的id
和person_id
上创建一个唯一约束。如果foo_bar
上的外键约束引用这些唯一约束,则自动满足条件。
ALTER TABLE foo ADD CONSTRAINT foo_id_person_unique
UNIQUE (person_id, id);
ALTER TABLE bar ADD CONSTRAINT bar_id_person_unique
UNIQUE (person_id, id);
ALTER TABLE foo_bar ADD person_id integer;
UPDATE foo_bar
SET person_id = foo.person_id
FROM foo
WHERE foo_bar.foo_id = foo_id;
ALTER TABLE foo_bar ALTER person_id SET NOT NULL;
ALTER TABLE foo_bar ADD CONSTRAINT foo_bar_foo_fkey
FOREIGN KEY (person_id, foo_id) REFERENCES foo (person_id, id);
ALTER TABLE foo_bar ADD CONSTRAINT foo_bar_bar_fkey
FOREIGN KEY (person_id, bar_id) REFERENCES bar (person_id, id);
然后从foo_bar
中删除原始外键约束。
我不会为foo_bar
使用人工主键,因为(foo_id, bar_id)
是一个自然的主键,可以保证不会多次输入任何关系。
您已经偶然发现了单个代理键的主要问题:当涉及到层次结构时(例如foo_bar对于foo和bar都是子对象,它们都是子对象),数据库系统无法强制执行一致性。
因此,使用复合键。某些东西(伪代码):
CREATE TABLE person (person_nr, name text,
PRIMARY KEY (person_nr));
CREATE TABLE foo (person_nr, foo_nr, comment text,
PRIMARY KEY (person_nr, foo_nr),
FOREIGN KEY person_nr REFERENCES person(person_nr));
CREATE TABLE bar (person_nr, bar_nr, comment text,
PRIMARY KEY (person_nr, bar_nr),
FOREIGN KEY person_nr REFERENCES person(person_nr));
CREATE TABLE foo_bar (person_id, foo_nr, bar_nr,
PRIMARY KEY (person_nr, foo_nr, bar_nr),
FOREIGN KEY (person_nr, foo_nr) REFERENCES foo(person_nr, foo_nr),
FOREIGN KEY (person_nr, bar_nr) REFERENCES bar(person_nr, bar_nr));
复合键的缺点是使连接更容易出错(即,您可能会混淆关键部分或错过密钥的一部分),但它们也通过强制执行一致性来使数据库更好。