循環外部キー参照を持つことは許容できますか?


29

外部キーフィールドの2つのテーブル間で循環参照を使用することはできますか?

そうでない場合、これらの状況をどのように回避できますか?

もしそうなら、どのようにデータを挿入できますか?

以下は、(私の意見では)循環参照が受け入れられる場所の例です。

CREATE TABLE Account
(
    ID INT PRIMARY KEY IDENTITY,
    Name VARCHAR(50)
)

CREATE TABLE Contact
(
    ID INT PRIMARY KEY IDENTITY,
    Name VARCHAR(50),
    AccountID INT FOREIGN KEY REFERENCES Account(ID)
)

ALTER TABLE Account ADD PrimaryContactID INT FOREIGN KEY REFERENCES Contact(ID)

2
もしそうなら、どのようにデータを挿入できるか」-使用されているDBMSに依存します。たとえば、Postgres、Oracle、SQLite、およびApache Derbyでは、これを可能にする遅延可能な制約が許可されています。他のDBMSでは運が悪かった(しかし、そもそもこのような制約の必要性については異議を唱えるだろう)
-a_horse_with_no_name

回答:


12

外部キーにヌル値可能フィールドを使用しているため、実際には、想定どおりに正しく機能するシステムを構築できます。Accountsテーブルに行を挿入するには、null PrimaryContactIDを持つAccountsへの挿入を許可しない限り、Contactsテーブルに行が存在する必要があります。アカウント行がまだ存在しない状態で連絡先行を作成するには、ContactsテーブルのAccountID列をNULL可能にする必要があります。これにより、アカウントは連絡先を持たず、連絡先はアカウントを持たなくなります。おそらくこれは望ましいかもしれませんが、おそらくそうではありません。

そうは言っても、私の個人的な好みは次のセットアップをすることです。

CREATE TABLE dbo.Accounts
(
    AccountID INT NOT NULL
        CONSTRAINT PK_Accounts
        PRIMARY KEY CLUSTERED
        IDENTITY(1,1)
    , AccountName VARCHAR(255)
);

CREATE TABLE dbo.Contacts
(
    ContactID INT NOT NULL
        CONSTRAINT PK_Contacts
        PRIMARY KEY CLUSTERED
        IDENTITY(1,1)
    , ContactName VARCHAR(255)
);

CREATE TABLE dbo.AccountsContactsXRef
(
    AccountsContactsXRefID INT NOT NULL
        CONSTRAINT PK_AccountsContactsXRef
        PRIMARY KEY CLUSTERED
        IDENTITY(1,1)
    , AccountID INT NOT NULL
        CONSTRAINT FK_AccountsContactsXRef_AccountID
        FOREIGN KEY REFERENCES dbo.Accounts(AccountID)
    , ContactID INT NOT NULL
        CONSTRAINT FK_AccountsContactsXRef_ContactID
        FOREIGN KEY REFERENCES dbo.Contacts(ContactID)
    , IsPrimary BIT NOT NULL 
        CONSTRAINT DF_AccountsContactsXRef
        DEFAULT ((0))
    , CONSTRAINT UQ_AccountsContactsXRef_AccountIDContactID
        UNIQUE (AccountID, ContactID)
);

CREATE UNIQUE INDEX IX_AccountsContactsXRef_Primary
ON dbo.AccountsContactsXRef(AccountID, IsPrimary)
WHERE IsPrimary = 1;

これにより、次のことが可能になります。

  1. Pieterの回答で推奨されているように、相互参照表を使用して連絡先とアカウントの関係を明確に示します。
  2. 健全で非円形の方法で参照整合性を維持します。
  3. インデックスを使用して、メンテナンスが容易な主要連絡先のリストを提供しIX_AccountsContactsXRef_Primaryます。このインデックスにはフィルターが含まれているため、それらをサポートするプラットフォームでのみ機能します。このインデックスはUNIQUEオプションで指定されるため、アカウントごとに1つのプライマリ連絡先しか存在できません。

たとえば、すべての連絡先のリストを「プライマリ」ステータスを示す列で表示し、各アカウントのリストの上部にプライマリ連絡先を表示する場合、次のようにします。

SELECT A.AccountName
    , C.ContactName
    , XR.IsPrimary
FROM dbo.Accounts A
    INNER JOIN dbo.AccountsContactsXRef XR ON A.AccountID = XR.AccountID
    INNER JOIN dbo.Contacts C ON XR.ContactID = C.ContactID
ORDER BY A.AccountName
    , XR.IsPrimary DESC
    , C.ContactName;

フィルタされたインデックスは、アカウントごとに複数のプライマリ連絡先の挿入を防ぎ、同時にプライマリ連絡先のリストをすばやく返す方法を提供します。IsActive連絡先がアカウントに関連付けられなくなった後でも、アカウントごとの連絡先の履歴を保持するために一意でないフィルター処理されたインデックスを使用して、別の列を簡単に想像できます。

ALTER TABLE dbo.AccountsContactsXRef
ADD IsActive BIT NOT NULL
CONSTRAINT DF_AccountsContactsXRef_IsActive
DEFAULT ((1));

CREATE INDEX IX_AccountsContactsXRef_IsActive
ON dbo.AccountsContactsXRef(IsActive)
WHERE IsActive = 1;

1
一般的に、循環参照は避けるべきだと思いますか?私は、それらは悪くなく、効果的な設計を達成するためにそれらを使用したと考えています。それらは、そうでなければ唯一の親エンティティでNULLを必要とし更新するという点で、削除を少し複雑にしますが、利便性のために支払うには低価格であることがわかります。私はそれらをPostgresで使用します.FKフィールドはnull可能ですので、NULLで行を作成し、FKフィールドを子テーブルからPKに更新して、OPで説明されているのとほぼ同じ機能を達成します
両生類

循環参照は、単に設計を不必要に複雑にする傾向があるという理由だけで嫌いであり、ほとんどの場合、トレードオフに値する大きなパフォーマンス上の利点はありません。私はOccamのRazorのファンであり、その結果、特定の問題に対する最も簡単な解決策に向かう傾向があります。
マックスヴァーノン

1
私はOccamのカミソリにすべて賛成していますが、上記の設計により、3番目の標準形式に必ずしも違反することなく、2番目のクエリまたは結合を回避できました。あなたのフィードバックに感謝します
両生類

6

いいえ、循環外部キー参照を持つことは受け入れられません。制約を絶えずドロップして再作成せずにデータを挿入することが不可能だからだけではありません。しかし、それは私が考えることができるありとあらゆるドメインの根本的に欠陥のあるモデルだからです。あなたの例では、AccountとContactの関係がNNではないドメインを考えることはできません。FK参照を含むjunctionテーブルがAccountとContactの両方に戻る必要があります。

CREATE TABLE Account
(
    ID INT PRIMARY KEY IDENTITY,
    Name VARCHAR(50)
)

CREATE TABLE Contact
(
    ID INT PRIMARY KEY IDENTITY,
    Name VARCHAR(50),
)

CREATE TABLE AccountContact
(
    AccountID INT FOREIGN KEY REFERENCES Account(ID),
    ContactID INT FOREIGN KEY REFERENCES Contact(ID),

    primary key(AccountID,ContactID)
)

5
データを挿入することは不可能です」-いいえ、不可能ではありません。制約を遅延可能として宣言するだけです。しかし、私は同意します:ほとんどすべての場合、循環参照は悪い設計です。
a_horse_with_no_name

3
@a_horse-SQL Serverで遅延参照を定義することはできません... Oracleでできることはわかっていますが、矛盾を指摘したかっただけです。
マックスヴァーノン

2
@MaxVernon:質問は、SQL Serverのだけではありませんし、Oracleだけでより多くのDBMSがサポート遅延可能制約ことがある-しかし、私は言ったように:私は設計自体が間違っているというのPieterに同意しません(と彼のソリューションは、はるかに理にかなって)
a_horse_with_no_name

4
いずれかの例の詳細は別として、一般的な用語では、相互(つまり「円形」)の参照整合性制約を持つことに関して必ずしも間違っているまたは「欠陥」はありません。これは、実際には結合依存関係の単なる例です。結合依存関係は、DBMSでそれらを実装できる場合、原則として良いことです。SQL DBMSでは、テーブル間の複雑な依存関係を実装するのはそれほど簡単ではありません。
nvogel

6
@ Pieter、1-1は結合依存関係の唯一の例ではなく、特に特別な場合でもありません。結合依存関係の制約が完全に理にかなっている場合があります。
-nvogel

1

外部オブジェクトがアカウントではなく、プライマリ連絡先を指すようにすることもできます。データは次のようになります。

CREATE TABLE Account
(
    ID INT PRIMARY KEY IDENTITY,
    Name VARCHAR(50)
)

CREATE TABLE Contact
(
    ID INT PRIMARY KEY IDENTITY,
    Name VARCHAR(50),
    AccountID INT FOREIGN KEY REFERENCES Account(ID)
)

CREATE TABLE AccountOwner (
    Other Stuff Here . . .
    PrimaryContactID INT FOREIGN KEY REFERENCES Contact(ID)
)
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.