3

私が取り組んでいるプロジェクトでは、1対9の関係を持つものとして定義する必要のあるテーブルがあり、データベースでそれを作成する最良の方法は何でしょうか。私は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)
);

ただし、これは、参照されているテーブルからデータをクリーンアップしたいときに、何も参照されていないときに問題を引き起こします。また、これが最初から悪いデータベース設計であったことにもかなり前向きでした。

私の他のアイデアは、1つの制約でテーブルを作成することです

CREATE TABLE template (
    id int,
    actual_id int,
    foreign_key0 int references other_table(id) );

ただし、ここでの問題は、他のテーブルへの参照が9つだけになるようにこれをどのように制限するかです。ストアドプロシージャ?プログラム的に?

最終的に、最初の方法に固執する場合は、単一の列を持つ別のテーブルでさまざまな外部キーをすべて選択し、それをother_tableのIDと比較する必要があると確信しています。私はこれをしたくありません。それは本当にばかげているようです。私は本当にそれを2番目の方法でやりたいのですが、これをどうやって行うのが最善かわかりません。

4

5 に答える 5

4

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<= 9DELETEchild


ところで、Javaまたはその他のかなり賢いORMでJPAを使用している場合は、親の子のコレクションのサイズを制限するだけです。

@Entity
public Parent {

    @Column
    @Size(min=9,max=9)
    private Collection<Child> collectionOfChildren;

}

データベースレベルでは強制されませんが、はるかに単純です。

于 2012-08-11T09:27:53.533 に答える
1

ただし、これは、参照されているテーブルからデータをクリーンアップしたいときに、何も参照されていないときに問題を引き起こします。

私がこれを正しく理解していれば、ダングリングポインタを自動的に削除したいと思います。助けてくれませ... REFERENCES other_table(id) ON DELETE CASCADEんか?

于 2012-08-10T21:44:27.680 に答える
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)
);

正規化された方法で、3番目のテーブル(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)
);
于 2012-08-11T10:59:36.883 に答える
0

これは制約を使用して実行できるとは思いません。postgresqlの最大行数に関する制約を作成する方法を参照してください。いくつかのアイデアのために。

以下に、以下の仮定でfooaとの関係のカウントを保持する例を記述しました。bar

  • foosとbarsは独立したエンティティであり、その存続期間は相互に依存しません
  • 関係は別のfoo2barマッピングテーブルに保存されます
  • マッピングは、最初にマッピングテーブルから削除する必要があります
  • 関係のないfooとbarの削除は無視されます

\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;
于 2012-08-11T08:26:08.467 に答える
0

別のアイデア。これは1対多の関係であり(nは9に制限されているだけです)、1対多の関係では、外部キー参照はあなたが持っているものとは逆の方法です。

したがって、FOREIGN KEY制約を逆にし(ボーナス:この方法で必要なのは1つだけです)、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) 
);
于 2012-08-11T10:49:34.607 に答える