このシナリオの適切な構造は、サブクラス/継承モデルであり、この回答で提案した概念の値の異種混合順序リストとほぼ同じです。
この質問で提案されているモデルは、Animal
エンティティにrace
すべてのタイプに共通のタイプ(つまり)とプロパティが含まれているという点で、実際には非常に近いものです。ただし、2つの小さな変更が必要です。
Cat_IDおよびDog_IDフィールドをそれぞれのエンティティから削除します。
ここで重要な概念であることすべてがあるAnimal
にかかわらず、race
:Cat
、Dog
、Elephant
、など。その出発点を考えると、以下の理由により、特定race
のAnimal
は本当に個別の識別子を必要としません。
Animal_ID
ユニークです
Cat
、Dog
、および任意の追加のrace
将来追加エンティティは、それだけでは完全に任意の特定を表すものではありませんAnimal
。親エンティティに含まれる情報と組み合わせて使用した場合にのみ意味がありAnimal
ます。
したがって、Animal_ID
中性Cat
、Dog
などのエンティティがPKとにFKバックの両方であるAnimal
エンティティ。
タイプの違いbreed
:
理由だけで2つのプロパティが同じ名前がありません共有必ずしも意味し、それらの性質が同じである名前があっても、同じであることを意味し、このような関係を。この場合は、何が本当に持っていることは、実際にあるCatBreed
とDogBreed
別々の「タイプ」として
最初のメモ
- SQLはMicrosoft SQL Serverに固有です(つまり、T-SQLです)。つまり、データ型はすべてのRDBMSで同じではないため、データ型には注意してください。たとえば、私は使用して
VARCHAR
いますが、標準のASCIIセット以外のものを格納する必要がある場合は、実際に使用する必要がありますNVARCHAR
。
- 「タイプ」テーブルのIDフィールドは(
Race
、CatBreed
、及びDogBreed
)ありません彼らは、静的なルックアップ値ですアプリケーション定数(すなわち、それらはアプリケーションの一部である)であるため、自動インクリメント(T-SQLの観点すなわちIDENTITYを)データベースでありenum
、C#(または他の言語)ではs として表されます。値が追加される場合、それらは制御された状況で追加されます。アプリケーションを介して受信するユーザーデータの自動インクリメントフィールドの使用を予約します。
- 私が使用する命名規則は、メインクラス名で始まり、サブクラス名が続く各サブクラステーブルに名前を付けることです。これは、テーブルの編成に役立つだけでなく、サブクラステーブルとメインエンティティテーブルの関係を(FKを見ずに)明確に示すのに役立ちます。
- ビューに関する注意事項については、最後の「最終編集」セクションを参照してください。
「レース」特有のアプローチとしての「ブリード」
この最初のテーブルセットは、ルックアップ/タイプテーブルです。
CREATE TABLE Race
(
RaceID INT NOT NULL PRIMARY KEY
RaceName VARCHAR(50) NOT NULL
);
CREATE TABLE CatBreed
(
CatBreedID INT NOT NULL PRIMARY KEY,
BreedName VARCHAR(50),
CatBreedAttribute1 INT,
CatBreedAttribute2 VARCHAR(10)
-- other "CatBreed"-specific properties as needed
);
CREATE TABLE DogBreed
(
DogBreedID INT NOT NULL PRIMARY KEY,
BreedName VARCHAR(50),
DogBreedAttribute1 TINYINT
-- other "DogBreed"-specific properties as needed
);
この2番目のリストは、メインの「動物」エンティティです。
CREATE TABLE Animal
(
AnimalID INT NOT NULL IDENTITY(1, 1) PRIMARY KEY,
RaceID INT NOT NULL, -- FK to Race
Name VARCHAR(50)
-- other "Animal" properties that are shared across "Race" types
);
ALTER TABLE Animal
ADD CONSTRAINT [FK_Animal_Race]
FOREIGN KEY (RaceID)
REFERENCES Race (RaceID);
この3番目のセットのテーブルは、以下のそれぞれRace
の定義を完了する補足的なサブクラスエンティティですAnimal
。
CREATE TABLE AnimalCat
(
AnimalID INT NOT NULL PRIMARY KEY, -- FK to Animal
CatBreedID INT NOT NULL, -- FK to CatBreed
HairColor VARCHAR(50) NOT NULL
-- other "Cat"-specific properties as needed
);
ALTER TABLE AnimalCat
ADD CONSTRAINT [FK_AnimalCat_CatBreed]
FOREIGN KEY (CatBreedID)
REFERENCES CatBreed (CatBreedID);
ALTER TABLE AnimalCat
ADD CONSTRAINT [FK_AnimalCat_Animal]
FOREIGN KEY (AnimalID)
REFERENCES Animal (AnimalID);
CREATE TABLE AnimalDog
(
AnimalID INT NOT NULL PRIMARY KEY, -- FK to Animal
DogBreedID INT NOT NULL, -- FK to DogBreed
HairColor VARCHAR(50) NOT NULL
-- other "Dog"-specific properties as needed
);
ALTER TABLE AnimalDog
ADD CONSTRAINT [FK_AnimalDog_DogBreed]
FOREIGN KEY (DogBreedID)
REFERENCES DogBreed (DogBreedID);
ALTER TABLE AnimalDog
ADD CONSTRAINT [FK_AnimalDog_Animal]
FOREIGN KEY (AnimalID)
REFERENCES Animal (AnimalID);
共有breed
タイプを使用するモデルは、「補足事項」セクションの後に示されています。
その他の注意事項
- の概念は
breed
、混乱の焦点のようです。jcolebrandによって(質問へのコメントで)breed
異なるrace
s 間で共有されるプロパティが提案されました。他の2つの答えは、モデルにそのように統合されています。ただし、の値breed
はの異なる値間で共有されないため、これは誤りですrace
。はい、私は他の2つの提案モデルrace
がの親にすることでこの問題を解決しようとしていることを認識していbreed
ます。これは関係の問題を技術的に解決しますが、一般的でないプロパティについて何をすべきかというモデリング全体の問題や、race
を持たないを処理する方法の解決には役立ちませんbreed
。ただし、そのようなプロパティがすべての場所に存在することが保証されている場合Animal
s、そのためのオプションも含めます(下記)。
- vijaypとDavidN(同一と思われる)によって提案されたモデルは、次の理由で機能しません。
- 彼らのどちらか
- 一般的でないプロパティを保存することを許可しない(少なくともの個々のインスタンスでは許可しない
Animal
)、または
- すべてののすべてのプロパティをエンティティに
race
格納する必要があります。Animal
これは、このデータを表す非常にフラットな(そしてほぼ非リレーショナルな)方法です。はい、人々はいつもこれを行っていますが、それはrace
、特定race
のレコードではなく、そのレコードの特定のレコードに関連付けられているフィールドを知るために、特定のプロパティの行ごとに多くのNULLフィールドを持つことを意味します。
- 彼らは追加のために許可されていない
race
のをAnimal
持っていない、将来的にbreed
プロパティとして。ALL場合でも、そしてAnimal
sが持っているbreed
、それが原因以前について指摘されているものに構造を変更しないでしょうbreed
。それbreed
に依存しているrace
(すなわちbreed
用Cat
と同じものではありませんbreed
のためにDog
)。
共通/共有プロパティアプローチとしての「ブリード」
ご注意ください:
以下のSQLは、上記のモデルと同じデータベースで実行できます。
Race
表は同じです
Breed
テーブルには新しいです
- 3つの
Animal
テーブルには、2
- 現在で
Breed
は一般的なプロパティであるとしてもRace
、メイン/親エンティティで(技術的に関係なく正しいとしても)言及しなかったのは正しくないようです。だから、両方のRaceID
とBreedID
で表現されていますAnimal2
。間の不整合を防止するためにRaceID
に留意Animal2
とBreedID
異なるためであることをRaceID
、私は両方でFKを追加したところRaceID, BreedID
で、これらのフィールドの一意性制約を参照するBreed
テーブル。私は通常、FKがUNIQUE CONSTRAINTを指すことを軽視しますが、これはそうするためのいくつかの有効な理由の1つです。UNIQUE CONSTRAINTは論理的には「代替キー」であり、これをこの用途に有効にします。また、Breed
テーブルにはまだのPKがあることに注意してくださいBreedID
。
- 結合フィールドでPKのみを使用し、UNIQUE CONSTRAINTを使用しない理由は、同じ
BreedID
値をの異なる値にわたって繰り返すことができるためですRaceID
。
- PKとUNIQUE CONSTRAINTの切り替えを行わない理由は、これがの唯一の使用方法で
BreedID
はない可能性があるためです。Breed
そのため、をRaceID
使用せずにの特定の値を参照することは可能です。
- 次のモデルは機能しますが、共有の概念に関して2つの潜在的な欠陥が
Breed
あります(そして、私が- Race
固有のBreed
テーブルを好む理由です)。
- のすべての値が
Breed
同じプロパティを持つという暗黙の仮定があります。このモデルでは、Dog
「品種」とElephant
「品種」の間で異なる特性を持つ簡単な方法はありません。ただし、これを行う方法はまだあります。これは「最終編集」セクションに記載されています。
Breed
複数の種族でa を共有する方法はありません。それが望ましいかどうか(または、動物の概念ではないが、おそらくこのタイプのモデルを使用する他の状況では可能か)はわかりませんが、ここではできません。
CREATE TABLE Race
(
RaceID INT NOT NULL PRIMARY KEY,
RaceName VARCHAR(50) NOT NULL
);
CREATE TABLE Breed
(
BreedID INT NOT NULL PRIMARY KEY,
RaceID INT NOT NULL, -- FK to Race
BreedName VARCHAR(50)
);
ALTER TABLE Breed
ADD CONSTRAINT [UQ_Breed]
UNIQUE (RaceID, BreedID);
ALTER TABLE Breed
ADD CONSTRAINT [FK_Breed_Race]
FOREIGN KEY (RaceID)
REFERENCES Race (RaceID);
CREATE TABLE Animal2
(
AnimalID INT NOT NULL IDENTITY(1, 1) PRIMARY KEY,
RaceID INT NOT NULL, -- FK to Race, FK to Breed
BreedID INT NOT NULL, -- FK to Breed
Name VARCHAR(50)
-- other properties common to all "Animal" types
);
ALTER TABLE Animal2
ADD CONSTRAINT [FK_Animal2_Race]
FOREIGN KEY (RaceID)
REFERENCES Race (RaceID);
-- This FK points to the UNIQUE CONSTRAINT on Breed, _not_ to the PK!
ALTER TABLE Animal2
ADD CONSTRAINT [FK_Animal2_Breed]
FOREIGN KEY (RaceID, BreedID)
REFERENCES Breed (RaceID, BreedID);
CREATE TABLE AnimalCat2
(
AnimalID INT NOT NULL PRIMARY KEY, -- FK to Animal
HairColor VARCHAR(50) NOT NULL
);
ALTER TABLE AnimalCat2
ADD CONSTRAINT [FK_AnimalCat2_Animal2]
FOREIGN KEY (AnimalID)
REFERENCES Animal2 (AnimalID);
CREATE TABLE AnimalDog2
(
AnimalID INT NOT NULL PRIMARY KEY,
HairColor VARCHAR(50) NOT NULL
);
ALTER TABLE AnimalDog2
ADD CONSTRAINT [FK_AnimalDog2_Animal2]
FOREIGN KEY (AnimalID)
REFERENCES Animal2 (AnimalID);
最終編集(できれば;-)
- のタイプ間で異なるプロパティを処理する可能性(およびその難しさ)に関しては、同じサブクラス/継承の概念をメインエンティティとして使用する
Breed
ことが可能Breed
です。この設定ではBreed
、テーブルは、すべてのタイプに共通する特性を持っているでしょうBreed
(ただのようなAnimal
テーブル)とRaceID
の種類を表すことになりBreed
(それはでないと同じようAnimal
表)。次にBreedCat
、などのサブクラステーブルがありますBreedDog
。小規模なプロジェクトの場合、これは「オーバーエンジニアリング」と見なされる可能性がありますが、メリットが得られる状況のオプションとして言及されています。
どちらの方法でも、エンティティ全体へのショートカットとしてビューを作成すると役立つ場合があります。たとえば、次のことを考慮してください。
CREATE VIEW Cats AS
SELECT an.AnimalID,
an.RaceID,
an.Name,
-- other "Animal" properties that are shared across "Race" types
cat.CatBreedID,
cat.HairColor
-- other "Cat"-specific properties as needed
FROM Animal an
INNER JOIN AnimalCat cat
ON cat.AnimalID = an.AnimalID
-- maybe add in JOIN(s) and field(s) for "Race" and/or "Breed"
- 論理エンティティの一部ではありませんが、少なくともレコードが挿入および更新されている時期を把握するために、テーブルに監査フィールドを含めることはかなり一般的です。だから実際的には:
CreatedDate
フィールドが追加されるだろうAnimal
テーブル。AnimalCat
トランザクション内で両方のテーブルに挿入される行を同時に実行する必要があるため、このフィールドはサブクラステーブル(など)では必要ありません。
LastModifiedDate
フィールドが追加されるだろうAnimal
テーブルとすべてのサブクラスのテーブル。このフィールドは、その特定のテーブルが更新された場合にのみ更新されます。特定AnimalCat
ので更新が発生したが、発生しなかった場合は、のフィールドのみが設定されます。Animal
AnimalID
LastModifiedDate
AnimalCat