複数のテーブルへの外部キー


127

私のデータベースには3つの関連テーブルがあります。

CREATE TABLE dbo.Group
(
    ID int NOT NULL,
    Name varchar(50) NOT NULL
)  

CREATE TABLE dbo.User
(
    ID int NOT NULL,
    Name varchar(50) NOT NULL
)

CREATE TABLE dbo.Ticket
(
    ID int NOT NULL,
    Owner int NOT NULL,
    Subject varchar(50) NULL
)

ユーザーは複数のグループに属しています。これは多対多の関係を介して行われますが、この場合は関係ありません。チケットは、dbo.Ticket.Ownerフィールドを介して、グループまたはユーザーが所有できます。

何でしょうMOST正しい方法は、チケットと、必要に応じてユーザーまたはグループ間のこの関係を記述?

チケットテーブルに、どのタイプがそれを所有しているかを示すフラグを追加する必要があると考えています。


私の考えでは、すべてのチケットはグループによって所有されています。ユーザーが1つのグループであることだけです。@ nathan-skerlモデルからの選択4。キーとしてGuidsを使用する場合、すべてが非常にうまく機能します
GraemeMiller '11

回答:


149

いくつかのオプションがあり、すべて「正確さ」と使いやすさが異なります。いつものように、適切な設計はニーズに依存します。

  • 単にチケットに2つの列、OwnedByUserIdとOwnedByGroupIdを作成し、各テーブルへのnull入力可能な外部キーを持つことができます。

  • ticket:userとticket:groupの両方の関係を有効にするM:M参照テーブルを作成できます。おそらく将来的には、単一のチケットを複数のユーザーまたはグループが所有できるようにしたいと思うでしょうか?この設計では、チケット単一のエンティティのみが所有する必要があるとは限りません。

  • すべてのユーザーに対してデフォルトグループを作成し、真のグループまたはユーザーのデフォルトグループのいずれかによって単純に所有されるチケットを持つことができます。

  • または(私の選択)、ユーザーとグループの両方のベースとして機能し、そのエンティティが所有するチケットを持つエンティティをモデル化します。

投稿したスキーマを使用した大まかな例を次に示します。

create table dbo.PartyType
(   
    PartyTypeId tinyint primary key,
    PartyTypeName varchar(10)
)

insert into dbo.PartyType
    values(1, 'User'), (2, 'Group');


create table dbo.Party
(
    PartyId int identity(1,1) primary key,
    PartyTypeId tinyint references dbo.PartyType(PartyTypeId),
    unique (PartyId, PartyTypeId)
)

CREATE TABLE dbo.[Group]
(
    ID int primary key,
    Name varchar(50) NOT NULL,
    PartyTypeId as cast(2 as tinyint) persisted,
    foreign key (ID, PartyTypeId) references Party(PartyId, PartyTypeID)
)  

CREATE TABLE dbo.[User]
(
    ID int primary key,
    Name varchar(50) NOT NULL,
    PartyTypeId as cast(1 as tinyint) persisted,
    foreign key (ID, PartyTypeId) references Party(PartyID, PartyTypeID)
)

CREATE TABLE dbo.Ticket
(
    ID int primary key,
    [Owner] int NOT NULL references dbo.Party(PartyId),
    [Subject] varchar(50) NULL
)

7
ユーザー/グループチケットのクエリはどのようになりますか?ありがとう。
ポールコン

4
グループテーブルとユーザーテーブルの永続的な計算列の利点は何ですか?Partyテーブルの主キーにより、グループIDとユーザーIDが重複しないことが既に保証されているため、外部キーはPartyIdのみに存在する必要があります。記述されたクエリは、とにかくPartyTypeNameからのテーブルを知る必要があります。
Arin Taylor

1
@ArinTaylorまたは永続化された列により、タイプUserのパーティーを作成し、それをdbo.Groupのレコードに関連付けることができなくなります。
Nathan Skerl

3
@paulkonこれは古い質問ですが、クエリは次のようSELECT t.Subject AS ticketSubject, CASE WHEN u.Name IS NOT NULL THEN u.Name ELSE g.Name END AS ticketOwnerName FROM Ticket t INNER JOIN Party p ON t.Owner=p.PartyId LEFT OUTER JOIN User u ON u.ID=p.PartyId LEFT OUTER JOIN Group g on g.ID=p.PartyID;になります。結果では、すべてのチケットの件名と所有者名が表示されます。
コーリーマクマホン

2
オプション4に関して、誰かがこれがアンチパターンかアンチパターンの解決策かを確認できますか?
inckka

31

@Nathan Skerlのリストの最初のオプションは、私がかつて作業したプロジェクトに実装されたもので、3つのテーブル間に同様の関係が確立されています。(そのうちの1つは、一度に1つずつ、他の2つを参照しました。)

そのため、参照元のテーブルには2つの外部キー列があり、1つのテーブルが正確に1つのテーブル(両方ではなく両方)を参照することを保証する制約もありました。

テーブルに適用すると、次のようになります。

CREATE TABLE dbo.[Group]
(
    ID int NOT NULL CONSTRAINT PK_Group PRIMARY KEY,
    Name varchar(50) NOT NULL
);

CREATE TABLE dbo.[User]
(
    ID int NOT NULL CONSTRAINT PK_User PRIMARY KEY,
    Name varchar(50) NOT NULL
);

CREATE TABLE dbo.Ticket
(
    ID int NOT NULL CONSTRAINT PK_Ticket PRIMARY KEY,
    OwnerGroup int NULL
      CONSTRAINT FK_Ticket_Group FOREIGN KEY REFERENCES dbo.[Group] (ID),
    OwnerUser int NULL
      CONSTRAINT FK_Ticket_User  FOREIGN KEY REFERENCES dbo.[User]  (ID),
    Subject varchar(50) NULL,
    CONSTRAINT CK_Ticket_GroupUser CHECK (
      CASE WHEN OwnerGroup IS NULL THEN 0 ELSE 1 END +
      CASE WHEN OwnerUser  IS NULL THEN 0 ELSE 1 END = 1
    )
);

ご覧のTicketとおり、テーブルには2つの列とがOwnerGroupありOwnerUser、どちらもnull入力可能な外部キーです。(他の2つのテーブルのそれぞれの列は、それに応じて主キーになります。)CK_Ticket_GroupUserチェック制約は、2つの外部キー列の1つだけが参照を含むことを保証します(他の列はNULLであり、そのため、両方ともNULL可能でなければなりません)。

Ticket.IDこの特定の実装では主キーをオンにする必要はありませんが、このようなテーブルに1つ置いても害はありません。)


1
これは私たちのソフトウェアにもあり、一般的なデータアクセスフレームワークを作成しようとしている場合は避けます。この設計により、アプリレイヤーの複雑さが増します。
Frank.Germain

4
私はSQLを初めて使用するので、これが間違っている場合は修正してください。ただし、この設計は、チケットの所有者タイプが2つだけ必要であると非常に確信している場合に使用するアプローチのようです。今後、3番目のチケット所有者タイプが導入された場合は、3番目のnull許容外部キー列をテーブルに追加する必要があります。
Shadoninja 2017

@Shadoninja:あなたは間違っていません。実際、それは完全に公正な方法だと思います。正当化されるこの種の解決策は一般的に大丈夫ですが、オプションを検討するとき、それは確かに最初に考えたものではありません-あなたが概説した理由のために。
Andriy M

2
@ Frank.Germainこの場合、2つの列RefIDに基づく一意の外部キーを使用できます。RefTypeここRefTypeで、はターゲットテーブルの固定識別子です。整合性が必要な場合は、トリガーまたはアプリレイヤーでチェックを実行できます。この場合、総称検索が可能です。SQLは、このようなFK定義を許可し、私たちの生活を容易にします。
djmj 2018年

2

さらに別のオプションはTicket、1つの列に所有エンティティタイプ(UserまたはGroup)を指定し、2番目の列に参照UserまたはGroupIDを指定し、外部キーを使用せず、トリガーに依存して参照整合性を適用することです。

ネイサンの優れたモデル(上記)に比べて、私がここで見る2つの利点:

  • より迅速な明快さとシンプルさ。
  • 書き込みがより簡単なクエリ。

1
しかし、これは外部キーを許可しませんか?私はまだ、現在のプロジェクトに適切なデザインを見つけようとしています。1つのテーブルが将来少なくとも3つ以上参照できるようになるでしょう
Can Rau

2

別のアプローチは、潜在的な各リソースタイプの列を含む関連付けテーブルを作成することです。この例では、2つの既存の所有者タイプのそれぞれに独自のテーブルがあります(つまり、参照するものがあることを意味します)。これが常に当てはまる場合は、次のようにすることができます。

CREATE TABLE dbo.Group
(
    ID int NOT NULL,
    Name varchar(50) NOT NULL
)  

CREATE TABLE dbo.User
(
    ID int NOT NULL,
    Name varchar(50) NOT NULL
)

CREATE TABLE dbo.Ticket
(
    ID int NOT NULL,
    Owner_ID int NOT NULL,
    Subject varchar(50) NULL
)

CREATE TABLE dbo.Owner
(
    ID int NOT NULL,
    User_ID int NULL,
    Group_ID int NULL,
    {{AdditionalEntity_ID}} int NOT NULL
)

このソリューションでは、データベースに新しいエンティティを追加するときに新しい列を追加し続け、@ Nathan Skerlによって示される外部キー制約パターンを削除して再作成します。このソリューションは@Nathan Skerlに非常に似ていますが、見た目は異なります(好みによります)。

新しい所有者の種類ごとに新しいテーブルを作成する予定がない場合は、潜在的な所有者ごとに外部キー列の代わりにowner_typeを含めるとよいでしょう。

CREATE TABLE dbo.Group
(
    ID int NOT NULL,
    Name varchar(50) NOT NULL
)  

CREATE TABLE dbo.User
(
    ID int NOT NULL,
    Name varchar(50) NOT NULL
)

CREATE TABLE dbo.Ticket
(
    ID int NOT NULL,
    Owner_ID int NOT NULL,
    Owner_Type string NOT NULL, -- In our example, this would be "User" or "Group"
    Subject varchar(50) NULL
)

上記の方法を使用すると、所有者タイプをいくつでも追加できます。Owner_IDには外部キー制約はありませんが、他のテーブルへの参照として使用されます。欠点は、スキーマに基づいてすぐに明らかになるわけではないため、所有者のタイプを確認するためにテーブルを参照する必要があることです。所有者のタイプが事前にわからず、他のテーブルにリンクされない場合にのみ、これをお勧めします。所有者のタイプを事前に知っている場合は、@ Nathan Skerlのようなソリューションを使用します。

SQLを間違えたら申し訳ありませんが、これを一緒に投げました


-4
CREATE TABLE dbo.OwnerType
(
    ID int NOT NULL,
    Name varchar(50) NULL
)

insert into OwnerType (Name) values ('User');
insert into OwnerType (Name) values ('Group');

これは、フラグを使用する代わりに、あなたが望むものを表現する最も一般的な方法だと思います。

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