SQLで行うのは簡単ではありませんが、不可能ではありません。DDLのみでこれを強制するには、DBMSにDEFERRABLE
制約を実装する必要があります。これを行うことができます(そして、それらを実装したPostgresで動作するようにチェックすることができます):
-- lets create first the 2 tables, A and B:
CREATE TABLE a
( aid INT NOT NULL,
bid INT NOT NULL,
CONSTRAINT a_pk PRIMARY KEY (aid)
);
CREATE TABLE b
( bid INT NOT NULL,
aid INT NOT NULL,
CONSTRAINT b_pk PRIMARY KEY (bid)
);
-- then table R:
CREATE TABLE r
( aid INT NOT NULL,
bid INT NOT NULL,
CONSTRAINT r_pk PRIMARY KEY (aid, bid),
CONSTRAINT a_r_fk FOREIGN KEY (aid) REFERENCES a,
CONSTRAINT b_r_fk FOREIGN KEY (bid) REFERENCES b
);
ここまでは「すべて」A
がゼロ、1つ、または多くに関連しB
、すべてB
がゼロ、1、または多くに関連する「通常の」設計A
です。
「全参加」制限が(から逆の順序での制約が必要A
とB
それぞれ参照しますR
)。持つFOREIGN KEY
(XからYへとXにYから)サークル(「鶏と卵」問題)が形成され、我々は、少なくとも、それらのいずれかを必要とする理由のと反対の方向に制約をDEFERRABLE
。このケースでは、二つの円を持っている(A -> R -> A
とB -> R -> B
私たちは2つの遅延可能制約が必要です:
-- then we add the 2 constraints that enforce the "total participation":
ALTER TABLE a
ADD CONSTRAINT r_a_fk FOREIGN KEY (aid, bid) REFERENCES r
DEFERRABLE INITIALLY DEFERRED ;
ALTER TABLE b
ADD CONSTRAINT r_b_fk FOREIGN KEY (aid, bid) REFERENCES r
DEFERRABLE INITIALLY DEFERRED ;
その後、データを挿入できることをテストできます。INITIALLY DEFERRED
は必要ないことに注意してください。制約を定義することもできますDEFERRABLE INITIALLY IMMEDIATE
が、その後、SET CONSTRAINTS
ステートメントを使用してトランザクション中にそれらを延期する必要があります。ただし、すべてのケースで、単一のトランザクションでテーブルに挿入する必要があります。
-- insert data
BEGIN TRANSACTION ;
INSERT INTO a (aid, bid)
VALUES
(1, 1), (2, 5),
(3, 7), (4, 1) ;
INSERT INTO b (aid, bid)
VALUES
(1, 1), (1, 2),
(2, 3), (2, 4),
(2, 5), (3, 6),
(3, 7) ;
INSERT INTO r (aid, bid)
VALUES
(1, 1), (1, 2),
(2, 3), (2, 4),
(2, 5), (3, 6),
(3, 7), (4, 1),
(4, 2), (4, 7) ;
END ;
SQLfiddleでテスト済み。
DBMSにDEFERRABLE
制約がない場合、1つの回避策は、A (bid)
およびをB (aid)
として定義することNULL
です。INSERT
手順/ステートメントは、最初に挿入する必要があろうA
とB
(にヌルを置くbid
とaid
、その後、それぞれ)に挿入R
した後から関連するヌルでない値に上記ヌル値を更新しますR
。
このアプローチでは、DBMSはDDLのみで要件を強制しませんが、すべてのINSERT
(およびUPDATE
and DELETE
およびMERGE
)プロシージャはそれに応じて考慮および調整する必要があり、ユーザーはそれらのみを使用するように制限する必要があり、テーブルへの直接書き込みアクセス権はありません。
FOREIGN KEY
制約に円を入れることは多くのベストプラクティスでは考慮されておらず、正当な理由により、複雑さもその1つです。たとえば、2番目のアプローチ(null許容列)では、DBMSに応じて、追加のコードで行の更新と削除を行う必要があります。たとえば、SQL Serverでは、ON DELETE CASCADE
FKサークルがある場合、カスケード更新およびカスケードは許可されないため、単に置くことはできません。
この関連質問の回答もお読みください:
特権のある子供と1対多の関係を築くには?
もう1つの3番目のアプローチ(上記の質問の私の答えを参照)は、循環FKを完全に削除することです。だから、(テーブルとコードの最初の部分を保ちA
、B
、R
のために(実際にそれを簡素化)はほぼ無傷でのみAとBのRからと外部キー)、我々は別のテーブルを追加するA
と、関連項目「1を持っている必要があります」を格納しますB
。したがって、A (bid)
列はA_one (bid)
BからAへの逆の関係でも同じように移動します。
CREATE TABLE a
( aid INT NOT NULL,
CONSTRAINT a_pk PRIMARY KEY (aid)
);
CREATE TABLE b
( bid INT NOT NULL,
CONSTRAINT b_pk PRIMARY KEY (bid)
);
-- then table R:
CREATE TABLE r
( aid INT NOT NULL,
bid INT NOT NULL,
CONSTRAINT r_pk PRIMARY KEY (aid, bid),
CONSTRAINT a_r_fk FOREIGN KEY (aid) REFERENCES a,
CONSTRAINT b_r_fk FOREIGN KEY (bid) REFERENCES b
);
CREATE TABLE a_one
( aid INT NOT NULL,
bid INT NOT NULL,
CONSTRAINT a_one_pk PRIMARY KEY (aid),
CONSTRAINT r_a_fk FOREIGN KEY (aid, bid) REFERENCES r
);
CREATE TABLE b_one
( bid INT NOT NULL,
aid INT NOT NULL,
CONSTRAINT b_one_pk PRIMARY KEY (bid),
CONSTRAINT r_b_fk FOREIGN KEY (aid, bid) REFERENCES r
);
1番目と2番目のアプローチとの違いは、循環FKがないため、カスケード更新とカスケード削除がうまく機能することです。「完全参加」の実施は、第2のアプローチのようにDDLだけではなく、適切な手順で実行する必要があります(INSERT/UPDATE/DELETE/MERGE
)。2番目のアプローチとの小さな違いは、すべての列をNULL不可として定義できることです。
別の4番目のアプローチ(上記の質問の@Aaron Bertrandの回答を参照)は、DBMSで使用可能な場合、フィルター処理された/部分的な一意のインデックスを使用することです(R
この場合、テーブルで2つ必要です)。これは3つのアプローチに非常に似ていますが、2つの余分なテーブルが必要ないという点が異なります。「合計参加」制約は、まだコードで適用する必要があります。