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


11

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

これはかなり簡単です、はい。ただし、各アイテムは特定のカテゴリ(たとえば、CarBoatPlane)に属しており、各カテゴリには特定の数の属性があります。

Car 構造:

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

Boat 構造:

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

Plane 構造:

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

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

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

最後に、私はすべての手段を持つことは、すべてのすべての属性一覧表示しながら、すべてのデータを保存するために固体構造を希望のアイテムユーザーは 1つのクエリで持っていることを。

サーバー側の言語で動的クエリハードコーディングすることもできましたが、これは間違っていて、あまり最適ではないと感じています。

追加情報

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

1.どのように多くの項目カテゴリー、3(すなわち、あなたのビジネスの文脈で関心のあるボート飛行機)以上は?

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

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

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

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

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

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

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

5.質問コメントの1つに対する以下の回答について:

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

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

いいえ、そうではありません。所有権が変更される可能性がありますが、私は前のトラック維持する必要はありません所有者

回答:


18

検討中の事業環境のあなたの説明によると、そこに存在するスーパータイプサブタイプを包含する構造項目 -the supertype-とそのそれぞれのカテゴリー、すなわち、ボート飛行機は(2とともにより知らされていませんでした) -サブタイプ—。

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

ビジネスルール

関連する概念スキーマの説明を始めるために、これまでに決定されたいくつかの最も重要なビジネスルール(分析を3つの開示されたカテゴリのみに制限し、できるだけ簡潔にする)を次のように定式化できます。

  • ユーザーは、ゼロ・ワン・オア・多く所有しているアイテムを
  • 項目は、正確に-1によって所有され、ユーザーの特定の瞬間に
  • 項目は、 1対多によって所有されるユーザー時間に明確なポイントで
  • 項目は正確に一つによって分類されているカテゴリー
  • 項目は、すべての回で、あります
    • またはボート
    • または飛行機

例示的なIDEF1Xダイアグラム

図1は、前の定式化をグループ化するために作成したIDEF1X 1ダイアグラムと、関連する他のビジネスルールを示しています。

図1-アイテムとカテゴリのスーパータイプ-サブタイプの構造

スーパータイプ

一方、スーパータイプであるItemは、すべてのカテゴリに共通のプロパティまたは属性を示します。

  • CategoryCodeを参照のことFOREIGN KEY(FK)として-specified Category.CategoryCodeサブタイプ等の機能識別器、すなわち、それは正確示すカテゴリ所与れるサブタイプのアイテムは、connected-なければならないし
  • OwnerIdにはポイントとFKとして-distinguished User.UserIdが、私はそれ役割名割り当てられた2、その特別な意味合いがよりaccurately-反映させるために、
  • フー
  • バー
  • バズ
  • CreatedDateTime

サブタイプ

一方、プロパティがすべての特定に関係することをカテゴリー、すなわち、

  • QuxCorge ;
  • GraultGarply、およびPlugh ;
  • XYZZYドサッWibbleFlob

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

識別子

次に、Item.ItemId PRIMARY KEY(PK)は、3を異なる役割名を持つサブタイプに移行しました。

  • CarId
  • BoatId
  • PlaneId

相互に排他的な関連付け

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

排他的なサブタイプのシンボルはサブタイプ、すなわち、相互に排他的であるという事実を描いて、具体的なアイテムの発生は、単一のサブタイプのインスタンスによって補完することができますいずれかの車、または1 、または1 ボート(決して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)  
);

実例で示されているように、スーパーエンティティタイプと各サブエンティティタイプは、対応するベーステーブルで表されます。

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

  • ()取付余分ホールドシステム制御サロゲート値に列サブタイプ放置(b)のテーブルには(C)で完全に余分

§(特にFOREIGN)KEY制約の定義(コメントで参照した状況)に関する問題やエラーを防止するために、以下に例示するように、手元のさまざまなテーブル間で発生する存在依存性を考慮することが非常に重要です。このSQL Fiddleでも提供した、説明用DDL構造内のテーブルの宣言順序。

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

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

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

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

有用なビューの宣言

上記で説明したような論理的な設計がある場合、1つ以上のビュー、つまり2つ以上の関連するベーステーブルに属する列を含む派生テーブルを作成することは非常に実用的です。このようにして、たとえば、「結合された」情報を取得する必要があるたびにすべての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;

--

当然のことながら、同様のアプローチに従って、1つのテーブル(これらの場合は派生テーブルBoatから「完全な」Plane情報を直接SELECTすることもできます。

あなた-ifそれは、次のビュー定義で結果セット-でNULLマークの有無については気にしないの後、次のことができ、例えば、「コレクト」のテーブルの列ItemCarBoatPlaneUserProfile

--

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;

--

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

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

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

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

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

  • 非常によく説明されています、ありがとうございました。ただし、複数のエイリアスを使用してステートメントを簡潔にしたくないため、一部の列との類似性のため、すべてのデータを一覧表示するときに、結果セットを操作する必要があると思いました。

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

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

同様のシナリオ

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

私はまた、サブタイプがこの(新しい)回答で相互に排他的ではないスーパータイプサブタイプクラスターを含むビジネス環境のソリューションを提案しました。


文末脚注

1 情報モデリングの統合定義 IDEF1X)は、1993年12月に米国国立標準技術研究所(NIST)によって標準として確立された、非常に推奨されるデータモデリング手法です。これは、(a)リレーショナルモデルの唯一の創始者、つまり EF Codd博士によって作成された理論的著作のいくつかにしっかりと基づいています。(b) PP Chen博士によって作成されたエンティティ関係ビュー。また、(c)Robert G. Brownが作成した論理データベース設計手法についても説明します。

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

3 IDEF1X標準では、キーの移行を「親エンティティまたは汎用エンティティの主キーを、その子エンティティまたはカテゴリエンティティに外部キーとして配置するモデリングプロセス」と定義しています


1
お問い合わせ内容がよくわかりませんが、DDLレイアウトに示されているように、ItemテーブルにはCategoryCode列が含まれています。「完全性と一貫性に関する考慮事項」というタイトルのセクションで述べたように、
MDCCL

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

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

1
問題の核心は、SQL実装(MySQL方言を含む)が、ASSERTIONS、手続き型アプローチ(TRANSACTIONSまたはTRIGGERS)、または冗長な方法での回避策の回避に役立つ強力でエレガントな宣言型ツールを適切にサポートしないことです。たとえば、CategoryColumnサブタイプを表すテーブルで不必要に繰り返します(論理[たとえば、変更の異常]と物理レベルの抽象化[たとえば、追加のインデックス、大きな構造など]ですべての影響があります)。
MDCCL

2
データベース管理システムのベンダー/開発者がASSERTIONS(このタスクに適したツール)を提供するまでは、(b)冗長な一連のアクション(b)よりも、(a)手続き型アプローチ(トランザクションまたはトリガー)を優先します。可能性です—個人的にはお勧めしません— もちろん、DBAは、関連性のあるデータベースで実行できる有効なデータ操作操作に関する権限を慎重に管理する必要があります。これは、データの整合性を維持するのに非常に役立ちます。
MDCCL

0

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

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


そして、私は製品を持つユーザーに参加しますか?
user5613506 2016

1
通常、製品のリストをフロントエンドに返すときにユーザー情報は必要ありません。製品情報が必要です。ユーザーと製品を結合して、返された製品行ごとに同じユーザー情報を返すことは意味がありません。したがって、最初に製品テーブルと適切なサブテーブル(Car、Boat ...)を結合して製品タイプでフィルタリングし、次にWHERE句を使用してユーザーでフィルタリングします。一般的には、Productsテーブル(UserテーブルのID列のFK)にOwnerIDが必要です。したがって、WHERE Owner = [Request.User]を追加します。
neManiac
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.