データベースの継承をモデル化するためのベストプラクティスは何ですか?
トレードオフとは何ですか(クエリ可能性など)?
(私はSQL Serverと.NETに最も興味がありますが、他のプラットフォームがこの問題にどのように対処するかについても理解したいと思います。)
データベースの継承をモデル化するためのベストプラクティスは何ですか?
トレードオフとは何ですか(クエリ可能性など)?
(私はSQL Serverと.NETに最も興味がありますが、他のプラットフォームがこの問題にどのように対処するかについても理解したいと思います。)
回答:
データベースの継承をモデル化する方法はいくつかあります。どちらを選択するかは、ニーズによって異なります。ここにいくつかのオプションがあります:
タイプごとのテーブル(TPT)
各クラスには独自のテーブルがあります。基本クラスにはすべての基本クラス要素があり、そこから派生する各クラスには独自のテーブルがあり、基本キーは基本クラステーブルへの外部キーでもあります。派生テーブルのクラスには、異なる要素のみが含まれています。
だから例えば:
class Person {
public int ID;
public string FirstName;
public string LastName;
}
class Employee : Person {
public DateTime StartDate;
}
次のようなテーブルになります:
table Person
------------
int id (PK)
string firstname
string lastname
table Employee
--------------
int id (PK, FK)
datetime startdate
階層ごとのテーブル(TPH)
すべての継承階層を表す単一のテーブルがあります。つまり、いくつかの列はおそらくスパースになります。これがどのタイプの行であるかをシステムに通知する弁別子列が追加されます。
上記のクラスを考えると、次の表になります。
table Person
------------
int id (PK)
int rowtype (0 = "Person", 1 = "Employee")
string firstname
string lastname
datetime startdate
行タイプ0(Person)の行の場合、startdateは常にnullになります。
Table-Per-Concrete(TPC)
各クラスには独自の完全に形成されたテーブルがあり、他のテーブルへの参照はありません。
上記のクラスを考えると、最終的に次のテーブルになります。
table Person
------------
int id (PK)
string firstname
string lastname
table Employee
--------------
int id (PK)
string firstname
string lastname
datetime startdate
適切なデータベース設計は、適切なオブジェクト設計に似ています。
オブジェクトを単純にシリアル化する以外の目的でデータベースを使用することを計画している場合(レポート、クエリ、マルチアプリケーションの使用、ビジネスインテリジェンスなど)、オブジェクトからテーブルへの単純なマッピングはお勧めしません。
多くの人はデータベーステーブルの行をエンティティと考えています(私はそれらの用語で何年も考えてきました)が、行はエンティティではありません。それは命題です。データベース関係(つまり、テーブル)は、世界についての事実の陳述を表します。行の存在は、事実が真であることを示します(逆に、その欠如は、事実が偽であることを示します)。
これを理解すると、オブジェクト指向プログラムの単一の型が12の異なる関係にわたって格納される可能性があることがわかります。また、さまざまなタイプ(継承、関連付け、集約、または完全に関連付けられていない)が部分的に単一のリレーションに格納される場合があります。
どんな事実を保存したいのか、どの質問に回答を求めたいのか、どのレポートを作成したいのかを自問するのが最善です。
適切なDB設計が作成されたら、オブジェクトをこれらの関係にシリアル化できるクエリ/ビューを作成するのは簡単です。
例:
ホテル予約システムでは、Jane DoeがSeaview Innの部屋を4月10〜12日に予約しているという事実を保存する必要がある場合があります。それは顧客エンティティの属性ですか?ホテルエンティティの属性ですか?それは、顧客とホテルを含むプロパティを持つ予約エンティティですか?それは、オブジェクト指向システムにおけるこれらのいずれかまたはすべての可能性があります。データベースでは、それはそれらのどれでもありません。それは単に裸の事実です。
違いを確認するには、次の2つのクエリを検討してください。(1)ジェーンドゥは来年何件のホテルを予約しますか?(2)シービューインで4月10日に予約される部屋数は?
オブジェクト指向システムでは、クエリ(1)は顧客エンティティの属性であり、クエリ(2)はホテルエンティティの属性です。それらは、APIでこれらのプロパティを公開するオブジェクトです。(ただし、これらの値が取得される内部メカニズムには、他のオブジェクトへの参照が含まれる場合があります。)
リレーショナルデータベースシステムでは、両方のクエリが予約関係を調べて番号を取得します。概念的には、他の「エンティティ」を気にする必要はありません。
したがって、適切なリレーショナルデータベースが構築されるのは、属性を持つエンティティを保存するのではなく、世界に関する事実を保存しようとすることです。そして、適切に設計されると、設計フェーズで思いもよらなかった有用なクエリを簡単に構築できます。これらのクエリを実行するために必要なすべての事実が適切な場所にあるからです。
Employment
すべての雇用とその開始日を収集するテーブルがあるはずです。では、の現在の雇用開始日を知ることEmployer
が重要である場合View
、それはの適切なユースケースになる可能性があります。(注:ニックネームの直後に「-」が付いているため、コメントで通知が届かなかったようです)
短い答え:そうではありません。
オブジェクトをシリアル化する必要がある場合は、ORMを使用するか、activerecordやprevaylenceなどを使用します。
データを格納する必要がある場合は、オブジェクトの設計の影響を受けないように、リレーショナルな方法で格納します(格納する内容に注意し、Jeffrey L Whitledgeが今言ったことに注意を払います)。
Brad Wilsonが述べたように、TPT、TPH、およびTPCパターンはあなたが行く方法です。しかし、いくつかの注意事項:
基本クラスから継承する子クラスは、データベース内の基本クラス定義への弱いエンティティと見なすことができます。つまり、それらは基本クラスに依存しており、基本クラスなしでは存在できません。私は何度も見てきましたが、その一意のIDは、子テーブルごとに格納され、同時にFKを親テーブルに保持します。1つのFKで十分であり、子テーブルとベーステーブル間のFKリレーションに対して削除時のカスケードを有効にするとさらに優れています。
TPTでは、ベーステーブルのレコードだけを見るだけでは、レコードが表す子クラスを見つけることができません。これは、すべてのレコードのリストをロードする場合に必要です( select
すべての子テーブルで実行する必要はありません)。これを処理する1つの方法は、子クラスのタイプを表す1つの列(TPHのrowTypeフィールドと同様)を持つことです。そのため、TPTとTPHを何らかの方法で混合します。
次の形状クラス図を保持するデータベースを設計するとします。
public class Shape {
int id;
Color color;
Thickness thickness;
//other fields
}
public class Rectangle : Shape {
Point topLeft;
Point bottomRight;
}
public class Circle : Shape {
Point center;
int radius;
}
上記のクラスのデータベース設計は次のようになります。
table Shape
-----------
int id; (PK)
int color;
int thichkness;
int rowType; (0 = Rectangle, 1 = Circle, 2 = ...)
table Rectangle
----------
int ShapeID; (FK on delete cascade)
int topLeftX;
int topLeftY;
int bottomRightX;
int bottomRightY;
table Circle
----------
int ShapeID; (FK on delete cascade)
int centerX;
int center;
int radius;
DBで設定できる継承には、主に2つのタイプがあります。エンティティごとのテーブルと階層ごとのテーブルです。
エンティティごとのテーブルは、すべての子クラスのプロパティを共有する基本エンティティテーブルがある場所です。次に、子クラスごとに、そのクラスに適用可能なプロパティのみを持つ別のテーブルを作成します。それらはPKによって1:1でリンクされています
階層ごとのテーブルは、すべてのクラスがテーブルを共有する場所であり、オプションのプロパティはnullにできます。それらは、レコードが現在保持しているタイプを示す数値である弁別子フィールドでもあります。
SessionTypeIDは識別子です
結合が必要ないため(識別値のみ)、階層ごとのターゲットの方がクエリが高速ですが、エンティティごとのターゲットは、何かのタイプを検出し、すべてのデータを取得するために複雑な結合を実行する必要があります。
編集:ここに表示する画像は、現在取り組んでいるプロジェクトのスクリーンショットです。アセットイメージは完全ではないため、何もありませんが、主にセットアップ方法を示すためであり、テーブル内に何を配置するかではありません。あれは君次第だ ;)。セッションテーブルは仮想コラボレーションセッション情報を保持し、関与するコラボレーションのタイプに応じて、いくつかのタイプのセッションになる可能性があります。
データベースを正規化すると、実際に継承が反映されます。パフォーマンスが低下する可能性がありますが、それが正規化の方法です。あなたはおそらくバランスを見つけるために良い常識を使わなければならないでしょう。
ORマッピングでは、継承は親テーブルにマップされ、親テーブルと子テーブルは同じ識別子を使用します
例えば
create table Object (
Id int NOT NULL --primary key, auto-increment
Name varchar(32)
)
create table SubObject (
Id int NOT NULL --primary key and also foreign key to Object
Description varchar(32)
)
サブオブジェクトには、オブジェクトとの外部キー関係があります。サブオブジェクト行を作成するときは、最初にオブジェクト行を作成し、両方の行でIDを使用する必要があります
編集:モデルの動作も検討している場合は、テーブル間の継承関係をリストし、各テーブルの動作を実装するアセンブリとクラス名を指定したTypeテーブルが必要になります
やり過ぎのように見えますが、それはすべて、それを何に使用するかによって異なります。