在我正在研究的项目中,我有一个表需要被定义为具有一到九个关系,我想知道在数据库中创建它的最佳方法是什么?我在PostgreSQL工作。
我最初的想法是创建一个表并且只是显式创建链接(请注意,actual_id是因为系统我必须虚拟化id,因为我需要唯一的表但我还需要知道模板的实际id是)
CREATE TABLE template (
id int,
actual_id int,
foreign_key0 int references other_table(id),
foreign_key1 int references other_table(id),
foreign_key2 int references other_table(id),
foreign_key3 int references other_table(id),
foreign_key4 int references other_table(id),
foreign_key5 int references other_table(id),
foreign_key6 int references other_table(id),
foreign_key7 int references other_table(id),
foreign_key8 int references other_table(id)
);
然而,当我想要在没有引用任何内容时从引用的表中清除数据时,这会产生一个问题。另外我非常肯定这是从一开始就糟糕的数据库设计。
我的另一个想法是,我只想用一个约束
制作表格CREATE TABLE template (
id int,
actual_id int,
foreign_key0 int references other_table(id) );
但是这里的问题是如何将此限制为仅对另一个表有9个引用?存储过程?编程?
最终如果我坚持第一种方式,我很确定我只需要将所有不同的foreign_key选择到另一个只有一列的表中,并将其与other_table的id进行比较。我不想这样做。看起来真的很蠢。我真的想第二种方式,但我不知道如何最好地解决这个问题。
答案 0 :(得分:3)
1:n关系总是可以反转为n:1。换句话说,而不是:
parent:field1 -> child1:id
parent:field2 -> child2:id
parent:field3 -> child3:id
....
parent:field9 -> child9
你总是可以写:
child1:parent_id -> parent:id
child2:parent_id -> parent:id
child3:parent_id -> parent:id
....
child9:parent_id -> parent:id
...并通过触发器或应用程序限制每个父母的子女数量。这是我强烈推荐的方法。您需要一个可延迟约束触发器,以允许您插入任何内容。
如果要在数据库中强制执行,请使用约束触发器。鉴于虚拟架构:
CREATE TABLE parent (id serial primary key);
CREATE TABLE child( id serial primary key, parent_id integer references parent(id) );
INSERT INTO parent (id) values ( DEFAULT );
INSERT INTO child ( parent_id )
SELECT p.id FROM parent p CROSS JOIN generate_series(1,9) x;
你可以写:
CREATE OR REPLACE FUNCTION children_per_parent() RETURNS TRIGGER AS $$
DECLARE
n integer;
BEGIN
IF TG_OP = 'INSERT' OR TG_OP = 'UPDATE' THEN
SELECT INTO n count(id) FROM child WHERE parent_id = NEW.parent_id;
IF n <> 9 THEN
RAISE EXCEPTION 'During % of child: Parent id=% must have exactly 9 children, not %',tg_op,NEW.parent_id,n;
END IF;
END IF;
IF TG_OP = 'UPDATE' OR TG_OP = 'DELETE' THEN
SELECT INTO n count(id) FROM child WHERE parent_id = OLD.parent_id;
IF n <> 9 THEN
RAISE EXCEPTION 'During % of child: Parent id=% must have exactly 9 children, not %',tg_op,NEW.parent_id,n;
END IF;
END IF;
RETURN NULL;
END;
$$ LANGUAGE 'plpgsql';
CREATE CONSTRAINT TRIGGER children_per_parent_tg
AFTER INSERT OR UPDATE OR DELETE ON child
DEFERRABLE INITIALLY DEFERRED
FOR EACH ROW EXECUTE PROCEDURE children_per_parent();
CREATE OR REPLACE parent_constrain_children() RETURNS trigger AS $$
DECLARE
n integer;
BEGIN
IF TG_OP = 'INSERT' THEN
SELECT INTO n count(id) FROM child WHERE parent_id = NEW.id;
IF n <> 9 THEN
RAISE EXCEPTION 'During INSERT of parent id=%: Must have 9 children, found %',NEW.id,n;
END IF;
END IF;
-- No need for an UPDATE or DELETE check, as regular referential integrity constraints
-- and the trigger on `child' will do the job.
RETURN NULL;
END;
$$ LANGUAGE 'plpgsql';
CREATE CONSTRAINT TRIGGER parent_limit_children_tg
AFTER INSERT ON parent
DEFERRABLE INITIALLY DEFERRED
FOR EACH ROW EXECUTE PROCEDURE parent_constrain_children();
请注意,上面有两个触发器。对孩子的触发很明显。需要在父项上触发,以防止插入没有任何子项的父项。
现在观察测试:
regress=# delete from child;
ERROR: During DELETE: Parent id 1 must have exactly 9 children, not 0
regress=# insert into child( parent_id) SELECT id FROM parent;
ERROR: During INSERT: Parent id 1 must have exactly 9 children, not 10
因为在事务提交时检查延迟约束触发器,而不是立即或在语句结束时检查,您仍然可以执行此操作:
regress# BEGIN;
BEGIN
regress# INSERT INTO parent (id) values ( DEFAULT ) RETURNING id;
id
----
2
INSERT 0 1
regress# insert into child ( parent_id ) SELECT p.id FROM parent p CROSS JOIN generate_series(1,9) x WHERE p.id = 4;
INSERT 0 9
regress# COMMIT;
COMMIT
...但如果您将“generate_series”max更改为8或10,或者完全不插入任何子项,则COMMIT将失败,例如:
regress=# commit;
ERROR: During INSERT: Parent id 5 must have exactly 9 children, not 8
如果您只要求每个家长拥有9个孩子的最多,而不是上述触发器中实现的完全 9个孩子,则可以删除{{1} },将DEFERRABLE INITIALLY DEFERRED
更改为<> 9
,并在<= 9
触发器中删除DELETE
处理程序。
child
方式更简单,尽管在数据库级别没有强制执行。
答案 1 :(得分:1)
但是当我想要在没有引用任何内容时从引用的表中清除数据时,这会产生一个问题。
如果我理解正确,你会想要悬挂指针自动删除。 ... REFERENCES other_table(id) ON DELETE CASCADE
会有帮助吗?
答案 2 :(得分:0)
我不认为这可以通过约束来完成,请参阅How to write a constraint concerning a max number of rows in postgresql?了解几个想法。
下面我写了一个例子,其中foo
保持与bar
的关系计数,并做出以下假设:
foo2bar
映射表\pset pager off
begin;
create table foo(id serial primary key, data text not null,
bar_count integer check(bar_count >= 0 and bar_count <= 3));
create table bar(id serial primary key, data text not null);
create table foo2bar(id serial primary key,
foo_id integer not null references foo(id),
bar_id integer not null references bar(id));
create or replace function trigger_update_bar_count() returns trigger
as $$
declare
v_bar_count integer := 0;
begin
if TG_OP = 'INSERT' then
select count(*) into v_bar_count from foo2bar where foo_id = new.foo_id;
update foo
set bar_count = v_bar_count + 1
where id = new.foo_id;
return new;
elsif TG_OP = 'DELETE' then
select count(*) into v_bar_count from foo2bar where foo_id = old.foo_id;
update foo
set bar_count = v_bar_count - 1
where id = old.foo_id;
return old;
end if;
end;
$$ language plpgsql;
create trigger trigger_foo2bar_1
before insert or delete on foo2bar
for each row execute procedure trigger_update_bar_count();
insert into foo(data) values('foo 1');
insert into bar(data) values('bar 1');
insert into foo2bar(foo_id, bar_id) values(currval('foo_id_seq'),
currval('bar_id_seq'));
insert into bar(data) values('bar 2');
insert into foo2bar(foo_id, bar_id) values(currval('foo_id_seq'),
currval('bar_id_seq'));
insert into bar(data) values('bar 3');
insert into foo2bar(foo_id, bar_id) values(currval('foo_id_seq'),
currval('bar_id_seq'));
insert into foo(data) values('foo 2');
insert into bar(data) values('bar 4');
insert into foo2bar(foo_id, bar_id) values(currval('foo_id_seq'),
currval('bar_id_seq'));
insert into bar(data) values('bar 5');
insert into foo2bar(foo_id, bar_id) values(currval('foo_id_seq'),
currval('bar_id_seq'));
insert into bar(data) values('bar 6');
insert into foo2bar(foo_id, bar_id) values(currval('foo_id_seq'),
currval('bar_id_seq'));
insert into foo(data) values('foo 3');
insert into bar(data) values('bar 7');
insert into foo2bar(foo_id, bar_id) values(currval('foo_id_seq'),
currval('bar_id_seq'));
insert into bar(data) values('bar 8');
insert into foo2bar(foo_id, bar_id) values(currval('foo_id_seq'),
currval('bar_id_seq'));
insert into bar(data) values('bar 9');
insert into foo2bar(foo_id, bar_id) values(currval('foo_id_seq'),
currval('bar_id_seq'));
-- deletes only mappings
delete from foo2bar where foo_id = 1;
delete from foo2bar where bar_id = 6;
-- This will raise because the check constraint will be violated
-- insert into bar(data) values('bar 10');
-- insert into foo2bar(foo_id, bar_id) values(currval('foo_id_seq'),
-- currval('bar_id_seq'));
select * from foo order by id;
select * from bar order by id;
select * from foo2bar order by id;
select foo.data as foo, bar.data as bar
from foo2bar
inner join foo on foo2bar.foo_id = foo.id
inner join bar on foo2bar.bar_id = bar.id
order by foo2bar.id
;
rollback;
答案 3 :(得分:0)
一个不同的想法。这是1对多关系(只有n限制为9),在1对多关系中,外键引用与您拥有的方式相反。
所以,反过来制定FOREIGN KEY
约束(奖励:你只需要这样一个)并添加counter
列和CHECK
约束来限制相关数量行最多为9:
CREATE TABLE template (
template_id int,
actual_id int,
PRIMARY KEY (template_id)
);
CREATE TABLE other_table (
other_table_id int,
template_id,
counter smallint NOT NULL,
--- other columns,
PRIMARY KEY (other_table_id),
UNIQUE KEY (template_id, counter),
CHECK (counter BETWEEN 1 AND 9),
FOREIGN KEY (template_id)
REFERENCES template (template_id)
);
答案 4 :(得分:0)
可维护和灵活的方法是规范化。而不只是这样做:
CREATE TABLE template (
id int,
actual_id int,
foreign_key0 int references other_table(id),
foreign_key1 int references other_table(id),
foreign_key2 int references other_table(id),
foreign_key3 int references other_table(id),
foreign_key4 int references other_table(id),
foreign_key5 int references other_table(id),
foreign_key6 int references other_table(id),
foreign_key7 int references other_table(id),
foreign_key8 int references other_table(id)
);
以标准化方式执行,引入第三个表(template_ assoc _other_table):
CREATE TABLE template (
id int not null primary key,
actual_id int -- I don't what is this
-- ...other fields here
);
create table template__assoc__other_table
(
template_id int not null references template(id),
other_table_id int not null references other_table(id),
constraint pk_template__assoc__other_table
primary key (template_id, other_table_id)
);