web-dev-qa-db-ja.com

異なる属性のセットを持つエンティティタイプをモデル化する方法は?

ユーザーアイテムの間に1対多(1:M)の関係を持つデータベースを再作成するのに問題があります。

これはかなり簡単です、はい。ただし、各アイテムは特定のカテゴリに属し(たとえば、ボートまたは飛行機)、各カテゴリは特定の数の属性。例:

Car構造:

+----+--------------+--------------+
| PK | Attribute #1 | Attribute #2 |
+----+--------------+--------------+

Boat構造:

+----+--------------+--------------+--------------+
| PK | Attribute #1 | Attribute #2 | Attribute #3 |
+----+--------------+--------------+--------------+

Plane構造:

+----+--------------+--------------+--------------+--------------+
| PK | Attribute #1 | Attribute #2 | Attribute #3 | Attribute #4 |
+----+--------------+--------------+--------------+--------------+

属性(列)の数がこのように多様であるため、最初はカテゴリごとに1つの個別のテーブルを作成することをお勧めします。そのため、複数のNULLを避けて、インデックスの使用。

最初は見栄えは良かったのですが、少なくともデータベース管理者としての私のささやかな経験では、作成時にアイテムカテゴリの間の関係をデータベースを通じて作成する方法を見つけることができませんでした。外部キー、データベースにテーブル名と列を明示的に通知します。

最後に、すべてのデータを格納するための強固な構造が必要ですが、1つのクエリですべてのアイテム a ユーザーのすべての属性をリストするすべての手段があります。

サーバーサイド言語でハードコード動的クエリを行うことができましたが、これは間違っていて、あまり最適ではないと感じています。

追加情報

これらは、MDCCLコメントに対する私の応答です。

1。ビジネスコンテキストに存在するアイテムカテゴリの数は3つ(つまり、ボート) および飛行機)以上?

実際、それは非常に単純です。合計で5つのカテゴリしかありません。

2。同じアイテムは常に同じユーザーに属します(つまり、指定されたアイテム特定のユーザーに「割り当て」られています変更できません)?

いいえ、変更される可能性があります。質問の架空のシナリオでは、ユーザーAがアイテム#1をユーザーBに販売するのようになるため、所有権を反映する必要があります。

3。カテゴリの一部またはすべてで共有される属性はありますか?

共有されていませんが、メモリから、すべてのカテゴリに少なくとも3つの属性が存在することがわかります。

4。ユーザーアイテムの間の関係のカーディナリティが多対多(M :N)1対多(1:M)ではなく?たとえば、次のビジネスルールの場合:A User owns zero-one-or-many ItemsおよびAn Item is owned by one-to-many Users

いいえ、アイテムは物理的なオブジェクトを表すためです。 ユーザーはそれらの仮想コピーを持ち、それぞれが一意の GUID v4 で識別されます

5。質問コメントの1つに対する次の応答について:

「質問の架空のシナリオでは、ユーザーAがユーザーBのためにアイテム#1を販売するので、所有権を反映する必要があります。」

いわば、アイテムの所有権の進化を追跡することを計画しているようです。このように、そのような現象についてどの属性を保存しますか?特定のアイテム?の特定のユーザー /所有者を示す属性の変更のみ

いいえ、そうでもありません。 所有権は変更される可能性がありますが、以前の所有者を追跡する必要はありません。

11
user5613506

検討中のビジネス環境の説明によると、supertype-subtype構造が存在し、Item—スーパータイプ—およびそれぞれのCategories、つまりCarBoatPlane(さらに、知られていない2つ)—サブタイプ—。

そのようなシナリオを管理するために私が従う方法について、以下で詳しく説明します。

ビジネスルール

関連する概念スキーマの説明を始めるために、これまでに決定された最も重要なビジネスルールの一部(分析を開示された3つのCategoriesのみ、物事をできるだけ簡潔にするために)次のように定式化できます。

  • A User owns zero-one-or-many Items
  • Itemは、特定の瞬間に1人のUserによって所有されます
  • Itemは、個別の時点で1対多のUsersが所有する場合があります
  • Itemは1つだけに分類されますCategory
  • Itemは常に、
    • Car
    • またはBoat
    • またはPlane

例示的なIDEF1Xダイアグラム

図1 IDEF1Xを表示します1 関連するように見える他のビジネスルールと一緒に前の定式化をグループ化するために作成した図:

Figure 1 - Item and Categories Supertype-Subtype Structure

スーパータイプ

一方、スーパータイプであるItemはプロパティを提示します または、すべてのCategoriesに共通する属性、つまり、

  • CategoryCode-Category.CategoryCodeを参照し、サブタイプとして機能するFOREIGN KEY(FK)として指定)discriminator、つまり、指定されたItemを接続する必要があるサブタイプの正確なCategoryを示します— 、
  • OwnerId-User.UserIdを指すFKとして識別されますが、ロール名を割り当てました2 その特別な影響をより正確に反映するために―
  • フー
  • バー
  • Bazおよび
  • CreatedDateTime

サブタイプ

一方、プロパティ 特定のすべてのCategoryに関連する、つまり、

  • QuxおよびCorge;
  • GraultGarplyおよびPlugh;
  • XyzzyThudWibbleおよびFlob ;

対応するサブタイプボックスに表示されます。

識別子

次に、Item.ItemIdPRIMARY KEY(PK)が移行されました3 異なる役割名を持つサブタイプに、つまり

  • CarId
  • BoatIdおよび
  • PlaneId

相互に排他的な関連付け

図示されているように、(a)各スーパータイプの出現と(b)その相補的なサブタイプのインスタンスの間には、カーディナリティの1対1(1:1)の関連付けまたは関係があります。

排他的なサブタイプ記号は、サブタイプが相互に排他的であること、つまり、具体的なItemの出現を単一のサブタイプインスタンスのみ:1つCar、または1つPlane、または1つBoat(決して2つ以上)。

 エンティティタイププロパティの一部に資格を付与するために、クラシックプレースホルダー名を使用しました。実際の名称は質問に含まれていないためです。

説明論理レベルのレイアウト

したがって、説明的な論理設計について説明するために、上記で表示および説明したIDEF1Xダイアグラムに基づいて、次のSQL-DDLステートメントを導出しました。

-- You should determine which are the most fitting 
-- data types and sizes for all your table columns 
-- depending on your business context characteristics.

-- Also, you should make accurate tests to define the 
-- most convenient INDEX strategies based on the exact 
-- data manipulation tendencies of your business context.

-- As one would expect, you are free to utilize 
-- your preferred (or required) naming conventions. 

CREATE TABLE UserProfile (
    UserId          INT      NOT NULL,
    FirstName       CHAR(30) NOT NULL,
    LastName        CHAR(30) NOT NULL,
    BirthDate       DATE     NOT NULL,
    GenderCode      CHAR(3)  NOT NULL,
    Username        CHAR(20) NOT NULL,
    CreatedDateTime DATETIME NOT NULL,
    --
    CONSTRAINT UserProfile_PK  PRIMARY KEY (UserId),
    CONSTRAINT UserProfile_AK1 UNIQUE ( -- Composite ALTERNATE KEY.
        FirstName,
        LastName,
        GenderCode,
        BirthDate
    ),
    CONSTRAINT UserProfile_AK2 UNIQUE (Username) -- ALTERNATE KEY.
);

CREATE TABLE Category (
    CategoryCode     CHAR(1)  NOT NULL, -- Meant to contain meaningful, short and stable values, e.g.; 'C' for 'Car'; 'B' for 'Boat'; 'P' for 'Plane'.
    Name             CHAR(30) NOT NULL,
    --
    CONSTRAINT Category_PK PRIMARY KEY (CategoryCode),
    CONSTRAINT Category_AK UNIQUE      (Name) -- ALTERNATE KEY.
);

CREATE TABLE Item ( -- Stands for the supertype.
    ItemId           INT      NOT NULL,
    OwnerId          INT      NOT NULL,
    CategoryCode     CHAR(1)  NOT NULL, -- Denotes the subtype discriminator.
    Foo              CHAR(30) NOT NULL,
    Bar              CHAR(30) NOT NULL,
    Baz              CHAR(30) NOT NULL,  
    CreatedDateTime  DATETIME NOT NULL,
    --
    CONSTRAINT Item_PK             PRIMARY KEY (ItemId),
    CONSTRAINT Item_to_Category_FK FOREIGN KEY (CategoryCode)
        REFERENCES Category    (CategoryCode),
    CONSTRAINT Item_to_User_FK     FOREIGN KEY (OwnerId)
        REFERENCES UserProfile (UserId)  
);

CREATE TABLE Car ( -- Represents one of the subtypes.
    CarId INT      NOT NULL, -- Must be constrained as (a) the PRIMARY KEY and (b) a FOREIGN KEY.
    Qux   CHAR(30) NOT NULL,
    Corge CHAR(30) NOT NULL,   
    --
    CONSTRAINT Car_PK         PRIMARY KEY (CarId),
    CONSTRAINT Car_to_Item_FK FOREIGN KEY (CarId)
        REFERENCES Item (ItemId)  
);

CREATE TABLE Boat ( -- Stands for one of the subtypes.
    BoatId INT      NOT NULL, -- Must be constrained as (a) the PRIMARY KEY and (b) a FOREIGN KEY.
    Grault CHAR(30) NOT NULL,
    Garply CHAR(30) NOT NULL,   
    Plugh  CHAR(30) NOT NULL, 
    --
    CONSTRAINT Boat_PK         PRIMARY KEY (BoatId),
    CONSTRAINT Boat_to_Item_FK FOREIGN KEY (BoatId)
        REFERENCES Item (ItemId)  
);

CREATE TABLE Plane ( -- Denotes one of the subtypes.
    PlaneId INT      NOT NULL, -- Must be constrained as (a) the PRIMARY KEY and (b) a FOREIGN KEY.
    Xyzzy   CHAR(30) NOT NULL,
    Thud    CHAR(30) NOT NULL,  
    Wibble  CHAR(30) NOT NULL,
    Flob    CHAR(30) NOT NULL,  
    --
    CONSTRAINT Plane_PK         PRIMARY KEY (PlaneId),
    CONSTRAINT Plane_to_Item_PK FOREIGN KEY (PlaneId)
        REFERENCES Item (ItemId)  
);

示されているように、上位エンティティタイプと各下位エンティティタイプは、対応するbaseテーブルで表されます。

適切なテーブルのPKとして制約されている列CarIdBoatIdおよびPlaneIdは、FK制約によって概念レベルの1対1の関連付けを表すのに役立ちます§ これは、ItemIdテーブルのPKとして制約されているItem列を指します。これは、実際の「ペア」では、スーパータイプ行とサブタイプ行の両方が同じPK値によって識別されることを意味します。したがって、それは、

  • (a)extra列をアタッチしてシステム制御のサロゲート値を保持する (b)にサブタイプを表すテーブルは(c)完全に不要です

§ (特にFOREIGN)KEY制約の定義(コメントで参照した状況)に関する問題やエラーを防ぐために、存在依存性this SQL Fiddle でも提供した、説明用DDL構造内のテーブルの宣言順序で例示されているように、さまざまなテーブルの間に配置します。

 たとえば、MySQLで構築されたデータベースのテーブルに AUTO_INCREMENT プロパティを使用して追加の列を追加します。

整合性と整合性に関する考慮事項

ビジネス環境では、(1)「スーパータイプ」の各行が常に対応する「サブタイプ」の対応物によって補完されていることを確認する必要があることを指摘することが重要です。 「サブタイプ」行は、「スーパータイプ」行の「弁別子」列に含まれる値と互換性があります。

declarativeの方法でそのような状況を強制することは非常にエレガントですが、残念ながら、私が知る限り、主要なSQLプラットフォームのいずれも、それを行うための適切なメカニズムを提供していません。したがって、 ACID TRANSACTIONS 内で手続き型コードを使用すると、データベースでこれらの条件が常に満たされるので非常に便利です。他のオプションはトリガーを使用することですが、いわば物事をだらしなくする傾向があります。

有用なビューの宣言

上記で説明したような論理的な設計がある場合、1つ以上のビュー、つまり、関連する2つ以上に属する列を含むderivedテーブルを作成することは非常に実用的です。 baseテーブル。このようにして、たとえば、「結合された」情報を取得する必要があるたびにすべてのJOINを書き込む必要なく、それらのビューから直接SELECTできます。

サンプルデータ

この点で、ベーステーブルには、以下に示すサンプルデータが「入力」されているとします。

--

INSERT INTO UserProfile 
    (UserId, FirstName, LastName, BirthDate, GenderCode, Username, CreatedDateTime)
VALUES
    (1, 'Edgar', 'Codd', '1923-08-19', 'M', 'ted.codd', CURDATE()),
    (2, 'Michelangelo', 'Buonarroti', '1475-03-06', 'M', 'michelangelo', CURDATE()),
    (3, 'Diego', 'Velázquez', '1599-06-06', 'M', 'd.velazquez', CURDATE());

INSERT INTO Category 
    (CategoryCode, Name)
VALUES
    ('C', 'Car'), ('B', 'Boat'), ('P', 'Plane');

-- 1. ‘Full’ Car INSERTion

-- 1.1 
INSERT INTO Item
    (ItemId, OwnerId, CategoryCode, Foo, Bar, Baz, CreatedDateTime)
VALUES
    (1, 1, 'C', 'This datum', 'That datum', 'Other datum', CURDATE());

 -- 1.2
INSERT INTO Car
    (CarId, Qux, Corge)
VALUES
    (1, 'Fantastic Car', 'Powerful engine pre-update!');

-- 2. ‘Full’ Boat INSERTion

-- 2.1
INSERT INTO Item
  (ItemId, OwnerId, CategoryCode, Foo, Bar, Baz, CreatedDateTime)
VALUES
  (2, 2, 'B', 'This datum', 'That datum', 'Other datum', CURDATE());

-- 2.2
INSERT INTO Boat
    (BoatId, Grault, Garply, Plugh)
VALUES
    (2, 'Excellent boat', 'Use it to sail', 'Everyday!');

-- 3 ‘Full’ Plane INSERTion

-- 3.1
INSERT INTO Item
  (ItemId, OwnerId, CategoryCode, Foo, Bar, Baz, CreatedDateTime)
VALUES
  (3, 3, 'P', 'This datum', 'That datum', 'Other datum', CURDATE());

-- 3.2
INSERT INTO Plane
    (PlaneId, Xyzzy, Thud, Wibble, Flob)
VALUES
    (3, 'Extraordinary plane', 'Traverses the sky', 'Free', 'Like a bird!');

--

次に、ItemCar、およびUserProfileから列を収集するのが有利なビューです。

--

CREATE VIEW CarAndOwner AS
    SELECT C.CarId,
           I.Foo,
           I.Bar,
           I.Baz,
           C.Qux,
           C.Corge,           
           U.FirstName AS OwnerFirstName,
           U.LastName  AS OwnerLastName
        FROM Item I
        JOIN Car C
          ON C.CarId = I.ItemId
        JOIN UserProfile U
          ON U.UserId = I.OwnerId;

--

当然のことながら、同様のアプローチに従って、「完全な」BoatおよびPlane情報を直接FROM1つの単一テーブルから選択することもできます (これらの場合、派生したもの)。

その後、結果セット内のNULLマークの存在を気にしない場合は、次のVIEW定義を使用して、たとえば、テーブルItemCarから列を「収集」できます。 BoatPlaneおよびUserProfile

--

CREATE VIEW FullItemAndOwner AS
    SELECT I.ItemId,
           I.Foo, -- Common to all Categories.
           I.Bar, -- Common to all Categories.
           I.Baz, -- Common to all Categories.
          IC.Name      AS Category,
           C.Qux,    -- Applies to Cars only.
           C.Corge,  -- Applies to Cars only.
           --
           B.Grault, -- Applies to Boats only.
           B.Garply, -- Applies to Boats only.
           B.Plugh,  -- Applies to Boats only.
           --
           P.Xyzzy,  -- Applies to Planes only.
           P.Thud,   -- Applies to Planes only.
           P.Wibble, -- Applies to Planes only.
           P.Flob,   -- Applies to Planes only.
           U.FirstName AS OwnerFirstName,
           U.LastName  AS OwnerLastName
        FROM Item I
        JOIN Category IC
          ON I.CategoryCode = IC.CategoryCode
   LEFT JOIN Car C
          ON C.CarId = I.ItemId
   LEFT JOIN Boat B
          ON B.BoatId = I.ItemId
   LEFT JOIN Plane P
          ON P.PlaneId = I.ItemId               
        JOIN UserProfile U
          ON U.UserId = I.OwnerId;

--

ここに示されているビューのコードは、単なる例示です。もちろん、いくつかのテスト演習と変更を行うと、手元にあるクエリの(物理的な)実行を加速するのに役立つ場合があります。さらに、ビジネスニーズに応じて、上記のビューから列を削除または追加する必要がある場合があります。

サンプルデータとすべてのビュー定義は this SQL Fiddle に組み込まれているため、「動作中」で監視できます。

データ操作:アプリケーションプログラムのコードと列のエイリアス

アプリケーションプログラムのコードの使用法(「サーバー側の特定のコード」が意味する場合)と列のエイリアスは、次のコメントで取り上げた他の重要なポイントです。

  • サーバー側の特定のコードで[a JOIN]の問題を回避することはできましたが、本当にしたくありません。そして、すべての列にエイリアスを追加すると、「ストレス」になる可能性があります。

  • 非常によく説明されています、ありがとうございました。ただし、私が疑ったように、いくつかの列との類似性のため、すべてのデータを一覧表示するときに結果セットを操作する必要があります。

アプリケーションプログラムコードの使用は結果セットのプレゼンテーション(またはグラフィカル)機能を処理するのに非常に適しているリソースですが、実行速度の問題を防ぐには、行ごとのデータ検索を回避することが最も重要であることを示すのは適切です。目的は、システムの動作を最適化できるように、SQLプラットフォームの(正確に)セットエンジンによって提供される堅牢なデータ操作手段を使用して、関連するデータセットを「フェッチ」することです。

さらに、エイリアスを使用して特定のスコープ内の1つ以上の列の名前を変更すると、ストレスがかかるように見えるかもしれませんが、個人的には、(i)コンテキスト化し、(ii)を明確化するのに役立つ非常に強力なツールと考えています関連する列に起因する意味および意図;したがって、これは、目的のデータの操作に関して完全に熟考する必要がある側面です。

同様のシナリオ

相互に排他的なサブタイプとのスーパータイプとサブタイプの関連付けを含む他の2つのケースについての私の見解を含むヘルプ この一連の投稿 および この投稿のグループ を見つけることもできます。

this(newer)answer でサブタイプがnot相互に排他的であるスーパータイプ-サブタイプクラスターを含むビジネス環境のソリューションも提案しました。


文末脚注

1 情報モデリングの統合定義IDEF1X )は、標準米国による1993年12月National Institute of Standards and Technology(NIST)。これは、(a) リレーショナルモデルsole originatorによって作成された理論的な作品のいくつか、つまり Dr。 EF Codd ; (b) エンティティ関係ビューDr。P. P.チェン によって開発されました。また、(c)Robert G. Brownが作成した論理データベース設計手法についても説明します。

2 IDEF1Xでは、ロール名は、FKプロパティ(または属性)に割り当てられる独特のラベルであり、それぞれのエンティティタイプのスコープ内で保持する意味を表現します。

3 IDEF1X標準では、key migrationを「親エンティティまたはジェネリックエンティティの主キーを、その子エンティティまたはカテゴリエンティティに外部キーとして配置するモデリングプロセス」と定義しています。

18
MDCCL

メインテーブルを製品と呼びましょう。これは共有属性をホストします。次に、Carテーブル、Planeテーブル、Boatテーブルがあるとします。これらの3つのテーブルには、ProductテーブルのID行にFK制約が設定されたProductIDキーがあります。それらすべてが必要な場合は、参加してください。車だけが必要な場合は、Cars with Productsを左結合します(または右結合の製品と車を結合しますが、常に左側結合を使用することをお勧めします)。

これは、階層データモデルと呼ばれます。サブテーブルの数が少ない場合は、長いテーブル(数百万の製品)で意味をなす場合があります。

0
neManiac