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();
上記には2つのトリガーがあることに注意してください。子供の引き金は明らかです。親のトリガーは、子のない親の挿入を防ぐために必要です。
次に、テストを観察します。
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」の最大値を8または10に変更するか、子を完全に挿入しないようにすると、COMMITは次のように失敗します。
regress=# commit;
ERROR: During INSERT: Parent id 5 must have exactly 9 children, not 8
上記のトリガーで実装された正確に9つの子ではなく、各親に最大9つの子が必要な場合は、を削除し、に変更して、トリガーのハンドラーを切り取ることができます。DEFERRABLE INITIALLY DEFERRED
<> 9
<= 9
DELETE
child
ところで、Javaまたはその他のかなり賢いORMでJPAを使用している場合は、親の子のコレクションのサイズを制限するだけです。
@Entity
public Parent {
@Column
@Size(min=9,max=9)
private Collection<Child> collectionOfChildren;
}
データベースレベルでは強制されませんが、はるかに単純です。