次のエンティティリレーションシップ図に示されているシナリオをSQLで実装するにはどうすればよいですか?
示されているように、すべてのA
エンティティタイプの出現は少なくとも1つB
の対応物(二重の接続線で示される)に関連している必要があり、その逆。次の3つのテーブルを作成する必要があることを知っています。
CREATE TABLE A
(
a INT NOT NULL,
CONSTRAINT A_PK PRIMARY KEY (a)
);
CREATE TABLE B
(
b INT NOT NULL,
CONSTRAINT B_PK PRIMARY KEY (b)
);
CREATE TABLE R
(
a INT NOT NULL,
b INT NOT NULL,
CONSTRAINT R_PK PRIMARY KEY (a, b),
CONSTRAINT R_to_A_FK FOREIGN KEY (a)
REFERENCES A (a),
CONSTRAINT R_to_B_FK FOREIGN KEY (b)
REFERENCES B (b)
);
しかし、総参加制約の実装についてはどうですか(つまり、enforcingそのeachA
またはB
のいずれかのインスタンスが関与しています最低1つ他との関係の発生)?
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
を参照)。反対方向(XからYおよびYからX)に_FOREIGN KEY
_制約があると、円(「鶏と卵」の問題)が形成され、少なくともDEFERRABLE
。この場合、2つの円(_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
_ステートメントを使用する必要があります。ただし、いずれの場合も、1つのトランザクションでテーブルに挿入する必要があります。
_-- 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
にそれぞれnullを入れる)、次にR
に挿入し、次に上記のnull値をR
からの関連する非null値に更新します。
このアプローチでは、DBMSはDDLだけで要件を強制しませんが、すべてのINSERT
(およびUPDATE
およびDELETE
およびMERGE
)プロシージャを考慮する必要がありますそれに応じて調整され、ユーザーはそれらのみを使用するように制限され、テーブルへの直接書き込みアクセス権を持たない必要があります。
_FOREIGN KEY
_制約に円があることは、多くのベストプラクティスでは考慮されていません。正当な理由により、複雑さもその1つです。たとえば、2番目のアプローチ(null許容列)では、DBMSによっては、行の更新と削除を追加のコードで実行する必要があります。たとえばSQL Serverでは、FKサークルが存在する場合、連鎖的な更新と削除は許可されないため、単に_ON DELETE CASCADE
_を置くことはできません。
この関連する質問の回答もお読みください:
特権を持つ子と1対多の関係を持つ方法は?
別の3番目のアプローチ(上記の質問の私の回答を参照)は、循環FKを完全に削除することです。したがって、コードの最初の部分(テーブルA
、B
、R
、およびRからAおよびBへの外部キーのみ)をほとんどそのままにしておく(実際にはコードを単純化する) 、A
用の別のテーブルを追加して、B
からの「1つ必要」関連アイテムを格納します。したがって、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の回答を参照)はfiltered/partial一意のインデックス(DBMSで使用可能な場合)(この場合、R
テーブルに2つ必要です)。これは、2つの追加のテーブルが必要ないことを除いて、3番目のアプローチと非常に似ています。 「合計参加」制約は、コードによって適用される必要があります。
直接はできません。まず、Bが存在しない状態でAのレコードを挿入することはできませんが、Aレコードがない場合はBレコードを作成できません。トリガーなどを使用して強制する方法はいくつかあります。すべての挿入を確認し、少なくとも1つの対応するレコードがABリンクテーブルに残っていることを削除する必要があります。