web-dev-qa-db-ja.com

2つの異なる種の動物に関するデータベースの設計のレビュー

SQL Server Express2017を使用して小さなデータベースを作成しています。

  • 2匹の動物(キツネとゾウ)がいます。
  • Foxesテーブルには、IdentifierSpeciesBodyLength、およびFurColourの列があります。
  • Elephantテーブルには、IdentifierSpeciesElephantWeightKG、およびTuskLengthCMがあります。
  • 各動物には「アイテム」のリストがあります。キツネとゾウの両方が共有するItemsテーブルがあります。
  • 現在、FoxとElephantの識別子の間に重複はありません。ただし、将来的には識別子の形式が変更される可能性があります(最悪のシナリオでは少し重複する可能性があります)。データベースはこれに対処するように設計する必要があります。

少し試行錯誤し、調査した結果、これが私が思いついたものです。

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
    )
);

これは許容できる設計ですか?あなたは何を変えますか?

ありがとう。

1
user182143

純粋な概念の観点からシナリオを分析すると、これまで誤って表現されてきた重要なエンティティタイプは動物であり、これは(a)関連するすべての関連付けをより正確に伝達するのに役立ちます。 (b)データベース全体を論理レベルでより明確に描写します。

この方法では:

  • FoxElephantは、Animalのエンティティサブタイプになり、エンティティのスーパータイプになります。

  • それぞれの特定の動物インスタンスはどちらか a Foxまたは an Elephantであるため、両方ではありません。サブタイプインスタンスは相互に排他的です。

  • 任意の動物オカレンス— フォックスまたはエレファント —は、0、1、または複数のアイテムオカレンスにリンクできます。一方、Itemインスタンスは、正確に1つのAnimal対応物にリンクされます—それはFoxまたはElephant—です。


スーパータイプ-サブタイプの関連付けは、一部の概念モデリング手法ではスーパークラス/サブクラス関係と呼ばれます。


説明的な論理DDL設計

したがって、対応する論理レベルの設計を次のように設定します。

-- 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データの「ルックアップ」テーブル

FoxesFurColoursに精通していません(たとえば、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値の正確な種を示しているため、データを解釈する人々は、検討中のItemFoxまたは_にリンクされているかどうかを確認できます。動物。したがって、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.ItemIDItems.FoxID、およびItems.ElephantID列の確認

Items.ItemID列は、ある種の派生可能な列のようです。つまり、そこに含まれる値は、テーブルの他の2つの列に含まれる値、つまりItems.FoxIDまたはItems.ElephanIDに沿って計算されます。 withItems.ItemSeq(これは不必要に関係しています)。あらゆる種類の列に対してそのようなアクションに頼ることは避けることを強くお勧めしますが、Items.ItemIDは主キー(PK)として制約されているため、私はアドバイスをより強調します。

このように、ビジネスドメインでItemが、そのItemSeqAnimalIdentifierの組み合わせによって一意に区別される場合、関連性(FoxまたはElephant)の場合、(AnimalId, ItemSeq)で2列の複合主キーを宣言することにより、Itemテーブルについて説明した論理配置を適応させて回避できます。派生可能なPKを使用した回避策。

スーパータイプとサブタイプの関連付けを含むその他のシナリオ

非常に異なる性質のビジネス環境で発生する典型的なデータ構造であるスーパータイプとサブタイプの関係の例をもっと見たい場合は、興味があるかもしれません。たとえば、次のタイトルの質問に対する私の回答です。

subtypes タグでグループ化された残りの投稿もご覧ください。

3
MDCCL