SQL Server Express2017を使用して小さなデータベースを作成しています。
Identifier
、Species
、BodyLength
、およびFurColour
の列があります。Identifier
、Species
、ElephantWeightKG
、およびTuskLengthCM
があります。Items
テーブルがあります。少し試行錯誤し、調査した結果、これが私が思いついたものです。
create table Foxes
(
Identifier varchar(50) not null constraint PK_FoxIdentifier primary key,
Species varchar(50),
BodyLength int,
FurColour varchar(50),
);
create table Elephants
(
Identifier varchar(50) not null constraint PK_ElephantsIdentifier primary key,
Species varchar(50),
ElephantWeightKG int,
TuskLengthCM int,
);
create table Items
(
ItemSeq int identity(1,1),
ItemID as isnull(FoxID, ElephantID) + '-' + cast(ItemSeq as varchar) persisted primary key,
FoxID varchar(50),
ElephantID varchar(50),
ItemDescription varchar(500),
foreign key (FoxID) references Foxes(Identifier) on delete cascade on update cascade,
foreign key (ElephantID) references Elephants(Identifier) on delete cascade on update cascade,
constraint CK_FoxIDElephantsItemIdentifier check
(
case when FoxID is null then 0 else 1 end +
case when ElephantID is null then 0 else 1 end = 1
)
);
これは許容できる設計ですか?あなたは何を変えますか?
ありがとう。
純粋な概念の観点からシナリオを分析すると、これまで誤って表現されてきた重要なエンティティタイプは動物であり、これは(a)関連するすべての関連付けをより正確に伝達するのに役立ちます。 (b)データベース全体を論理レベルでより明確に描写します。
この方法では:
FoxとElephantは、Animalのエンティティサブタイプになり、エンティティのスーパータイプになります。
それぞれの特定の動物インスタンスはどちらか a Foxまたは an Elephantであるため、両方ではありません。サブタイプインスタンスは相互に排他的です。
任意の動物オカレンス— フォックスまたはエレファント —は、0、1、または複数のアイテムオカレンスにリンクできます。一方、Itemインスタンスは、正確に1つのAnimal対応物にリンクされます—それはFoxまたはElephant—です。
注:スーパータイプ-サブタイプの関連付けは、一部の概念モデリング手法ではスーパークラス/サブクラス関係と呼ばれます。
したがって、対応する論理レベルの設計を次のように設定します。
-- You should determine which are the most fitting
-- data types and sizes for all your table columns depending
-- on the applicable business context characteristics.
-- Also, you should make accurate tests to define the most
-- convenient physical-level index strategies.
-- As one would expect, you are free to make use of
-- your preferred (or required) naming conventions.
CREATE TABLE Species (
SpeciesCode CHAR(1) NOT NULL,
Name CHAR(30) NOT NULL,
--
CONSTRAINT Species_PK PRIMARY KEY (SpeciesCode),
CONSTRAINT Species_AK UNIQUE (Name)
);
-- “Populating” the Species table:
INSERT INTO Species
(SpeciesCode, Name)
VALUES
('F', 'Fox'),
('E', 'Elephant');
CREATE TABLE Animal ( -- Represents the supertype.
AnimalId INT NOT NULL IDENTITY (1,1), -- Column meant to enclosed the Identifiers as established in the business context of relevance.
SpeciesCode CHAR(1) NOT NULL, --Stands for the discriminator property.
CreatedDateTime DATETIME NOT NULL,
--
CONSTRAINT Animal_PK PRIMARY KEY (AnimalId),
CONSTRAINT Animal_to_Species_FK FOREIGN KEY (SpeciesCode)
REFERENCES Species (SpeciesCode)
);
CREATE TABLE Fox ( -- Conveys one of the subtypes.
FoxId INT NOT NULL, -- No need for system-controlled surrogate values (e.g. those generated with the IDENTITY property) in this column.
BusinessId VARCHAR(50) NOT NULL, -- This column is supposed to retain the Identifiers as established in the business context of relevance.
BodyLength INT NOT NULL,
FurColour VARCHAR NOT NULL,
--
CONSTRAINT Fox_PK PRIMARY KEY (FoxId),
CONSTRAINT Fox_AK UNIQUE (BusinessId),
CONSTRAINT Fox_to_Animal_FK FOREIGN KEY (FoxId)
REFERENCES Animal (AnimalId)
);
CREATE TABLE Elephant ( -- Represents the other subtype.
ElephantId INT NOT NULL, -- No need for system-controlled surrogate values (e.g. those generated with the IDENTITY property) in this column either.
BusinessId VARCHAR(50) NOT NULL, -- This column is supposed to retain the Identifiers as established in the business context of relevance.
Weight INT NOT NULL,
TuskLength INT NOT NULL,
--
CONSTRAINT Elephant_PK PRIMARY KEY (ElephantId),
CONSTRAINT Elephant_AK UNIQUE (BusinessId),
CONSTRAINT Elephant_to_Animal_FK FOREIGN KEY (ElephantId)
REFERENCES Animal (AnimalId)
);
CREATE TABLE Item (
ItemSeq INT NOT NULL IDENTITY (1,1),
AnimalId INT NOT NULL,
Description VARCHAR(500) NOT NULL,
CreatedDateTime DATETIME NOT NULL,
--
CONSTRAINT Item_PK PRIMARY KEY (ItemSeq),
CONSTRAINT Item_to_Animal_FK FOREIGN KEY (AnimalId)
REFERENCES Animal (AnimalId)
);
このdb <> fiddle には、実際の動作を確認できるように、上記のDDLレイアウトが含まれています。
適切なテーブルのPKとして制約されている列FoxId
およびElephantId
は、AnimalId
テーブルのPKとして制約されているAnimal
列を指すFK制約によって、概念レベルの1対1の関連付けを表すのに役立ちます。 。これは、実際の「ペア」では、スーパータイプとサブタイプの両方の行が同じPK値で識別されることを意味します。したがって、(a)システム制御の代理値を保持するための追加の列を(b)サブタイプを表すテーブルは(c)に言及するのは適切です。 完全に不要。
ご覧のとおり、特定のAnimal
行がデータベースに挿入された正確な時点を知ることが重要であると考えているため、Animal.CreatedDateTime
を追加しました。
このようにして、Item
行とFox
またはElephant
行の間の関連付けは、対応するAnimal
行と適切な制約を介して「間接的に」確立されます。
Item.AnimalId
列は、(1)Items.FoxId
用に1つの列、(2)Items.ElephantId
用に1つの列を持つことを防ぎ、それらに伴うすべてのあいまいさと不必要な複雑さを伴います(たとえば、 データ操作)に与える影響でNULLマークを受け入れる 、Items
テーブルに追加したようなCHECK制約を追加するなど)。
FurColour
データの「ルックアップ」テーブルFoxesのFurColoursに精通していません(たとえば、Fox標本によって提示されるFurColoursの数、またはその方法がわかりません。 FurColoursは構成されているなど)が、この種の情報の「ルックアップ」の役割を果たすテーブルを組み込むことを評価したい場合があります。このテーブルは、外部キーから参照できます。 Fox
テーブルの制約。
ビジネスコンテキストでは、(1)各「スーパータイプ」行が常に対応する「サブタイプ」対応物によって補完されていることを確認し、(2)次のことを保証する必要があることに言及することが最も重要です。 「サブタイプ」行は、「スーパータイプ」行の「ディスクリミネーター」列に含まれる値と互換性があります。
アサーションを使用して、このような状況を宣言型の方法で適用するのは非常にエレガントですが、残念ながら、主要なSQLプラットフォームのいずれも、これらの強力な手段(このタイプの適切なツール)を適切にサポートしていません。ジョブ)。したがって、 ACID TRANSACTIONs 内のproceduralコードを使用すると、データベースでこれらの条件が常に満たされるため、非常に便利です。他のオプションはTRIGGER(手続き型も)を採用することですが、それらは物事を乱雑にする傾向があります。
FoxID
テーブルのElephantID
列とItems
列が提供する目的の1つは、Item行がリンクされているAnimal行のタイプを判別するのに役立つようですが、その必要性は、次のように例示されるように、ビュー(つまり、派生テーブル)によってより適切に対処されます。
CREATE VIEW ItemWithAnimal AS
SELECT I.ItemSeq,
A.AnimalId,
A.SpeciesCode,
I.Description,
I.CreatedDateTime AS ItemCreatedDateTime,
A.CreatedDateTime AS AnimalCreatedDateTime
FROM Item I
JOIN Animal A
ON I.AnimalId = A.AnimalId;
…ここで、SpeciesCode
列の値は、懸念されるAnimalId
値の正確な種を示しているため、データを解釈する人々は、検討中のItemがFoxまたは_にリンクされているかどうかを確認できます。動物。したがって、Item情報を取得する場合は、Item
ベーステーブルの代わりに、この派生テーブルまたはビューから直接SELECTできます。
「完全な」FoxまたはElephant情報を取得するビューがあると、次のようになります。
CREATE VIEW FullFox AS
SELECT A.AnimalId,
F.BusinessId,
F.BodyLength,
F.FurColor,
A.CreatedDateTime
FROM Fox F
JOIN Animal A
ON F.FoxId = A.AnimalId;
明らかに、このビューは、たとえば、Animal
テーブルがFox
またはElephant
テーブルによって「共有」されることを意図したより多くの列で構成されている場合、より有益ですが、ビューの利点を説明する価値があります。
上記のビューは db <> fiddle 以前にリンクされていたものにも含まれています。
Items.ItemID
、Items.FoxID
、およびItems.ElephantID
列の確認Items.ItemID
列は、ある種の派生可能な列のようです。つまり、そこに含まれる値は、テーブルの他の2つの列に含まれる値、つまりItems.FoxID
またはItems.ElephanID
に沿って計算されます。 withItems.ItemSeq
(これは不必要に関係しています)。あらゆる種類の列に対してそのようなアクションに頼ることは避けることを強くお勧めしますが、Items.ItemID
は主キー(PK)として制約されているため、私はアドバイスをより強調します。
このように、ビジネスドメインでItemが、そのItemSeqとAnimalのIdentifierの組み合わせによって一意に区別される場合、関連性(FoxまたはElephant)の場合、(AnimalId, ItemSeq)
で2列の複合主キーを宣言することにより、Item
テーブルについて説明した論理配置を適応させて回避できます。派生可能なPKを使用した回避策。
非常に異なる性質のビジネス環境で発生する典型的なデータ構造であるスーパータイプとサブタイプの関係の例をもっと見たい場合は、興味があるかもしれません。たとえば、次のタイトルの質問に対する私の回答です。
subtypes タグでグループ化された残りの投稿もご覧ください。