SQL 標準アサーションが必要だと思いますが、これは (残念ながら) 実際の DBMS ではほとんど実装されていません。
すべての回答は、TableA、TableB、および TableC と呼ばれる 3 つのプライマリ テーブルがあり、それぞれに独自の ID 列が含まれていることに同意しています。
TableA (A_ID PRIMARY KEY, ...)
TableB (B_ID PRIMARY KEY, ...)
TableC (C_ID PRIMARY KEY, ...)
単一の B 値が複数の A 親エントリを持つことができるかどうかは、問題の説明から明らかではありません。単一の C が複数の B 親エントリを持つことができることは明らかです。B が 1 つの A に関連付けられている場合、TableB の設計は次のように変更できます。
TableB (B_ID, ..., A_ID REFERENCES TableA)
B をいくつかの異なる A に関連付けることができる場合、接続は結合テーブルによって最もよく表されます。
A_and_B (A_ID REFERENCES TableA,
B_ID REFERENCES TableB,
PRIMARY KEY (A_ID, B_ID)
)
また、B に関連付けられた C が、B に関連付けられたすべての A に対して同じでなければならないのか、または異なる A が同じ B を参照できるのか、および A1 の B に関連付けられた C のセットが説明から明らかではないA2 の B に関連付けられている C のセットとは異なる場合があります。(もちろん、1 つの B が 1 つの A にしか関連付けられない場合、この問題は意味がありません。)
この回答の目的のために、任意の B が単一の A に関連付けられていると仮定するので、TableB の構造には A_ID が外部キーとして含まれています。単一の C を複数の B に関連付けることができるため、関連する構造は新しい結合テーブルです。
B_and_C (B_ID REFERENCES TableB,
C_ID REFERENCES TableC,
PRIMARY KEY (B_ID, C_ID)
)
アサーションを単純化すると (延期可能性と即時性に関するルールを省略して)、次のようになります。
CREATE ASSERTION assertion_name CHECK ( <search_condition> )
したがって、一連の設計上の決定事項が得られたら、データを検証するためのアサーションを作成できます。テーブル TableA、TableB (外部キー A_ID を使用)、TableC、および B_and_C が与えられた場合、完全な A 全体での特定の C_ID の出現回数が 1 であることが要件です。
CREATE ASSERTION only_one_instance_of_c_per_a CHECK
(
NOT EXISTS (
SELECT A_ID, COUNT(C_ID)
FROM TableB JOIN B_and_C USING (C_ID)
GROUP BY A_ID
HAVING COUNT(C_ID) > 1
)
)
[修正: これはより正確だと思います:
CREATE ASSERTION only_one_instance_of_c_per_a CHECK
(
NOT EXISTS (
SELECT A_ID, C_ID, COUNT(*)
FROM TableB JOIN B_and_C USING (C_ID)
GROUP BY A_ID, C_ID
HAVING COUNT(*) > 1
)
)
]
結合条件のセットは、テーブルの接続方法に関する他のルールによって異なりますが、全体的な制約構造は同じままです。特定の A_ID に対して特定の C_ID への参照が複数存在してはなりません。
以下のコメントで、meandmycode のメモ:
自分のデザインに欠陥があるような気がします。私の現実世界の論理は、「B」には常に少なくとも 1 つの子「C」があるというものです。'B' がその子をアタッチする前に存在しなければならないことを考えると、これは意味がありません。現在、データベースでは、少なくとも 1 つの「C」を持たずに「B」を「A」に関連付けることができます。そのため、「B」を修正して、その子を参照するフィールドを持つようにします。追加の「C」の子コレクションを持つだけでなく、「B」で指定されたプライマリ「C」も含めることができるコレクションがありますが、これは間違っています。
「1 つ以上の子」ルールとゼロ以上のルールを推論するデータベース パターンはありますか?
モデルに問題があると思います。新しく作成された B を参照する C が既に存在している必要がある場合、特に C が既存の B のみを参照する必要がある場合、B を作成するのは困難です。「鶏と卵」という言葉が思い浮かびます。したがって、通常、このようなコンテキストでは、B が 0 個以上の C を持つことを許可します。
TableB に A_ID 外部キーがあるかどうか、または A_and_B のようなリンク テーブルがあるかどうかはまだ規定していません。外部キーがある場合、参照先の A を作成するまで、おそらく B を作成できません。
テーブル B に 1 つの C ID を含めることは良い考えではないと思います。非対称処理 (より難しい SQL) になります。また、その 1 つの C を削除する必要がある場合は、現在あるテーブルから他の C 参照の 1 つが削除されるように更新してから、B レコードの値を更新する必要があることも意味します。それについて礼儀正しくするために、それは面倒です。
さまざまな回答に示されている行に沿って、見ている実際のテーブル構造を定義するために質問を修正する必要があると思います。3 つのドットを使用して、他の関係のない列を表すことができます。私が提案したアサーションは、おそらくある種のトリガーとして実装する必要があります。これは、DBMS 固有の表記法になります。
ブリーフ (A)、サブミッション (B)、およびメンバー (C) の修正された説明から、1 つのサブミッションが 1 つのブリーフのみに適用されることは明らかです。そのため、サブミッションは、サブミッションであるブリーフを識別する単純な外部キーを持つことができます。為に。また、メンバーは、特定のブリーフの 1 つの提出物に対してのみ共同作業できます。提出物とメンバーを識別するための列を含む「submission_collaborators」のテーブルがあり、その組み合わせが主キーで、各列が外部キーです。
Briefs(Brief_ID, ...)
Submissions(Submission_ID, Brief_ID REFERENCES Briefs, ...)
Members(Member_ID, ...)
Submission_Collaborators(Submission_ID REFERENCES Submissions,
Member_ID REFERENCES Members,
PRIMARY KEY (Submission_ID, Member_ID)
)
したがって、要件は、次のクエリが行を返さないようにすることです。
SELECT s.brief_id, c.member_id, COUNT(*)
FROM submissions AS s JOIN submission_collaborators AS c
ON s.submission_id = c.submission_id
GROUP BY s.brief_id, c.member_id
HAVING COUNT(*) > 1
これは、CREATE ASSERTION (2 番目のバリアント) に埋め込んだのと同じクエリです。追加情報 (簡単なタイトル、提出タイトル、メンバー名、さまざまな日付など) を掘り出すこともできますが、問題の核心は、表示されるクエリがデータを返してはならないということです。