データベースで継承をモデル化するためのベスト プラクティスは何ですか?
どのようなトレードオフがありますか (クエリ可能性など)?
(私は 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) の行では、開始日は常に null になります。
テーブル・パー・コンクリート (TPC)
各クラスには、他のテーブルへの参照がない独自の完全に形成されたテーブルがあります。
上記のクラスを考えると、次のテーブルになります。
table Person
------------
int id (PK)
string firstname
string lastname
table Employee
--------------
int id (PK)
string firstname
string lastname
datetime startdate
適切なデータベース設計は、適切なオブジェクト設計とは異なります。
オブジェクトを単純にシリアル化する以外の目的でデータベースを使用する予定がある場合 (レポート、クエリ、マルチアプリケーションの使用、ビジネス インテリジェンスなど)、オブジェクトからテーブルへの単純なマッピングはお勧めしません。
多くの人は、データベース テーブルの行をエンティティと考えていますが (私は何年もこの用語で考えてきました)、行はエンティティではありません。提案です。データベースのリレーション (つまり、テーブル) は、世界に関する何らかの事実のステートメントを表します。行が存在することは事実が真であることを示します (逆に、行が存在しないことは事実が偽であることを示します)。
これを理解すると、オブジェクト指向プログラムの単一の型が多数の異なるリレーションにまたがって格納される可能性があることがわかります。また、さまざまなタイプ (継承、関連付け、集約によって結合されたもの、またはまったく関連がないもの) が部分的に 1 つのリレーションに格納される場合があります。
どのような事実を保存したいのか、どのような質問に対する回答が必要なのか、どのようなレポートを作成したいのかを自問するのが最善です。
適切な DB 設計が作成されると、オブジェクトをそれらの関係にシリアライズできるようにするクエリ/ビューを作成するのは簡単なことです。
例:
ホテルの予約システムでは、Jane Doe が 4 月 10 ~ 12 日に Seaview Inn の部屋を予約したという事実を保存する必要がある場合があります。それは顧客エンティティの属性ですか? ホテルエンティティの属性ですか?顧客とホテルを含むプロパティを持つ予約エンティティですか? それは、オブジェクト指向システムのそれらの一部またはすべてである可能性があります。データベースでは、それらのどれでもありません。それはただの事実です。
違いを確認するには、次の 2 つのクエリを検討してください。(1) ジェーン・ドウは来年のホテルの予約をいくつ持っていますか? (2) Seaview Inn で 4 月 10 日に予約された部屋数は?
オブジェクト指向システムでは、クエリ (1) は顧客エンティティの属性であり、クエリ (2) はホテル エンティティの属性です。これらは、API でこれらのプロパティを公開するオブジェクトです。(ただし、明らかに、これらの値を取得する内部メカニズムには、他のオブジェクトへの参照が含まれる場合があります。)
リレーショナル データベース システムでは、両方のクエリが予約関係を調べて番号を取得します。概念的には、他の「エンティティ」を気にする必要はありません。
このように、属性を持つエンティティを格納しようとするのではなく、世界に関する事実を格納しようとすることによって、適切なリレーショナル データベースが構築されます。そして、適切に設計されれば、設計段階では思いもよらなかった有用なクエリを簡単に構築できます。これらのクエリを満たすために必要なすべての事実が適切な場所にあるからです。
短い答え:あなたはしません。
オブジェクトをシリアル化する必要がある場合は、ORM を使用するか、activerecord や prevaylence などを使用してください。
データを保存する必要がある場合は、オブジェクト設計の影響を受けずに、リレーショナルな方法で保存します (保存する内容に注意し、Jeffrey L Whitledge の発言に注意してください)。
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)
)
SubObject は、Object に対して外部キー関係を持っています。サブオブジェクト行を作成するときは、最初にオブジェクト行を作成し、両方の行で Id を使用する必要があります
編集:モデルの動作も検討している場合は、テーブル間の継承関係をリストし、各テーブルの動作を実装するアセンブリとクラス名を指定したタイプ テーブルが必要です。
やり過ぎのように思えますが、それはすべてあなたがそれを何に使いたいかによって異なります!
SQL ALchemy (Python ORM) を使用すると、2 種類の継承を行うことができます。
私が経験したのは、単一テーブルを使用し、判別列を使用することです。たとえば、Sheep データベース (冗談ではありません!) では、すべての Sheep が 1 つのテーブルに格納され、Rams と Ewes はそのテーブルの性別列を使用して処理されました。
したがって、すべての Sheep を照会して、すべての Sheep を取得できます。または、Ram のみでクエリを実行すると、Rams のみが取得されます。また、雄羊のみになれるリレーション (つまり、羊の父) などを作成することもできます。
データベースを正規化すると、実際に継承が反映されます。パフォーマンスの低下があるかもしれませんが、ノーマライズではそうです。バランスを見つけるには、おそらく常識を働かせる必要があります。
一部のデータベース エンジンは、 Postgresのようにネイティブに継承メカニズムを既に提供していることに注意してください。ドキュメントを参照してください。
たとえば、上記の応答で説明されている個人/従業員システムを次のようにクエリします。
/* これは、すべての個人または従業員の名前を示します */ 人から名前を選択します。 /* これは全従業員のみの開始日を示しています */ 従業員から開始日を選択します。
それはあなたのデータベースの選択であり、特に賢くする必要はありません!