SQLの総参加制約との多対多の関係の実装


17

次のエンティティ関係図に示されているシナリオを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)
    );

しかし、何の実装について総参加制約(すなわち、施行することをそれぞれのいずれかのインスタンスAまたはBで関与している1つの最小他との関係の発生)?

回答:


16

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です。

「全参加」制限が(から逆の順序での制約が必要ABそれぞれ参照しますR)。持つFOREIGN KEY(XからYへとXにYから)サークル(「鶏と卵」問題)が形成され、我々は、少なくとも、それらのいずれかを必要とする理由のと反対の方向に制約をDEFERRABLE。このケースでは、二つの円を持っている(A -> R -> AB -> 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手順/ステートメントは、最初に挿入する必要があろうAB(にヌルを置くbidaid、その後、それぞれ)に挿入Rした後から関連するヌルでない値に上記ヌル値を更新しますR

このアプローチでは、DBMSはDDLのみで要件を強制しませんが、すべてのINSERT(およびUPDATEand DELETEおよびMERGE)プロシージャはそれに応じて考慮および調整する必要があり、ユーザーはそれらのみを使用するように制限する必要があり、テーブルへの直接書き込みアクセス権はありません。

FOREIGN KEY制約に円を入れることは多くのベストプラクティスでは考慮されておらず、正当な理由により、複雑さもその1つです。たとえば、2番目のアプローチ(null許容列)では、DBMSに応じて、追加のコードで行の更新と削除を行う必要があります。たとえば、SQL Serverでは、ON DELETE CASCADEFKサークルがある場合、カスケード更新およびカスケードは許可されないため、単に置くことはできません。

この関連質問の回答もお読みください:
特権のある子供と1対多の関係を築くには?


もう1つの3番目のアプローチ(上記の質問の私の答えを参照)は、循環FKを完全に削除することです。だから、(テーブルとコードの最初の部分を保ちABRのために(実際にそれを簡素化)はほぼ無傷でのみ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つの余分なテーブルが必要ないという点が異なります。「合計参加」制約は、まだコードで適用する必要があります。


4番目のアプローチ(少し隠されています)は実際には完璧です。例として、postgresqlのpostgresql.org/docs/9.6/static/indexes-partial.html例11-3を参照してください。
ダニーロ

@Danilo合計1人の参加者(postgreの例の成功など)に基づいて、合計で最大1人の参加者を確保することが完璧であることを確認しました。少なくとも1つの成功、つまりこのスレッドでの実際の質問があることを確認することが、どのように役立つかわかりません。詳しく説明してもらえますか?
アレクサンダーミハイロフ

3

直接することはできません。まず、Bが既に存在しないとAのレコードを挿入できませんが、Aレコードがない場合はBレコードを作成できません。トリガーなどを使用して強制する方法はいくつかあります。すべての挿入を確認し、ABリンクテーブルに少なくとも1つの対応するレコードが残っていることを削除する必要があります。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.