データベースの継承をどのように効果的にモデル化しますか?


131

データベースの継承をモデル化するためのベストプラクティスは何ですか?

トレードオフとは何ですか(クエリ可能性など)?

(私はSQL Serverと.NETに最も興味がありますが、他のプラットフォームがこの問題にどのように対処するかについても理解したいと思います。)


14
「ベストプラクティス」に興味がある場合、ほとんどの答えは単に正しくありません。ベストプラクティスでは、RDbとアプリは独立していると規定しています。それらは完全に異なる設計基準を持っています。したがって、データベースでの「継承のモデル化」(または単一のアプリやアプリ言語に合わせてRDbをモデル化すること)は非常に悪い習慣であり、知識がなく、基本的なRDb設計ルールに違反し、それを無効にします。
PerformanceDBA、


6
@PerformanceDBAでは、DBモデルでの継承を回避するための提案は何ですか?50種類の教師がいて、その特定の教師をクラスに関連付けたいとします。継承を受けずにそれをどのように達成しますか?
svlada 2015年

1
@svlada。これはRDbに実装するのが簡単なので、「継承」が必要です。質問して、表の定義と例を含めてください。詳しくお答えします。オブジェクト指向でそれを行うと、それは王室の混乱になります。
PerformanceDBA、

回答:


162

データベースの継承をモデル化する方法はいくつかあります。どちらを選択するかは、ニーズによって異なります。ここにいくつかのオプションがあります:

タイプごとのテーブル(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

23
「どちらを選択するかはニーズに依存します」-選択の理由が質問の核心を形成していると思うので、詳しく説明してください。
アレックス

12
質問に対する私のコメントを参照してください。存在するRdb技術用語に面白い新しい名前を使用すると、混乱が生じます。「TPT」はスーパータイプ-サブタイプです。「TPH」は正規化されていない、大きなエラーです。「TPH」はさらに正規化されておらず、もう1つの重大なエラーです。
PerformanceDBA、

45
非正規化は常にエラーであると想定するのはDBAだけです。:)
Brad Wilson、

7
非正規化によってパフォーマンスが向上する場合があることを認めますが、これは完全にDBMSのデータの論理構造と物理構造の分離が不完全(または存在しない)ためである場合もあります。残念ながら、商用DBMSの大部分はこの問題に悩まされています。@PerformanceDBAは正しいです。正規化不足は判断の誤りであり、データの一貫性を犠牲にして速度を上げます。悲しいことに、DBMSが適切に設計されていれば、DBAまたは開発者が選択する必要のない選択です。ちなみに私はDBAではありません。
ケネスコクラン2013

6
@ブラッドウィルソン。開発者だけが「パフォーマンスのため」などに非正規化します。多くの場合、これは非正規化ではなく、真実は正規化されていないことです。非正規化または非正規化は誤りであり、事実であり、理論に裏付けられており、何百万人もが経験しており、「推定」ではありません。
PerformanceDBA、

133

適切なデータベース設計は、適切なオブジェクト設計に似ています。

オブジェクトを単純にシリアル化する以外の目的でデータベースを使用することを計画している場合(レポート、クエリ、マルチアプリケーションの使用、ビジネスインテリジェンスなど)、オブジェクトからテーブルへの単純なマッピングはお勧めしません。

多くの人はデータベーステーブルの行をエンティティと考えています(私はそれらの用語で何年も考えてきました)が、行はエンティティではありません。それは命題です。データベース関係(つまり、テーブル)は、世界についての事実の陳述を表します。行の存在は、事実が真であることを示します(逆に、その欠如は、事実が偽であることを示します)。

これを理解すると、オブジェクト指向プログラムの単一の型が12の異なる関係にわたって格納される可能性があることがわかります。また、さまざまなタイプ(継承、関連付け、集約、または完全に関連付けられていない)が部分的に単一のリレーションに格納される場合があります。

どんな事実を保存したいのか、どの質問に回答を求めたいのか、どのレポートを作成したいのかを自問するのが最善です。

適切なDB設計が作成されたら、オブジェクトをこれらの関係にシリアル化できるクエリ/ビューを作成するのは簡単です。

例:

ホテル予約システムでは、Jane DoeがSeaview Innの部屋を4月10〜12日に予約しているという事実を保存する必要がある場合があります。それは顧客エンティティの属性ですか?ホテルエンティティの属性ですか?それは、顧客とホテルを含むプロパティを持つ予約エンティティですか?それは、オブジェクト指向システムにおけるこれらのいずれかまたはすべての可能性があります。データベースでは、それはそれらのどれでもありません。それは単に裸の事実です。

違いを確認するには、次の2つのクエリを検討してください。(1)ジェーンドゥは来年何件のホテルを予約しますか?(2)シービューインで4月10日に予約される部屋数は?

オブジェクト指向システムでは、クエリ(1)は顧客エンティティの属性であり、クエリ(2)はホテルエンティティの属性です。それらは、APIでこれらのプロパティを公開するオブジェクトです。(ただし、これらの値が取得される内部メカニズムには、他のオブジェクトへの参照が含まれる場合があります。)

リレーショナルデータベースシステムでは、両方のクエリが予約関係を調べて番号を取得します。概念的には、他の「エンティティ」を気にする必要はありません。

したがって、適切なリレーショナルデータベースが構築されるのは、属性を持つエンティティを保存するのではなく、世界に関する事実を保存しようとすることです。そして、適切に設計されると、設計フェーズで思いもよらなかった有用なクエリを簡単に構築できます。これらのクエリを実行するために必要なすべての事実が適切な場所にあるからです。


12
+1最後に、無知の海にある本物の知識の島(そして彼らの野心以外のものを学ぶことを拒否する)。これは魔法ではありません。RDbがRDbの原則を使用して設計されている場合、「クラス」を「マップ」または「プロジェクト」するのは簡単です。RDbをクラスベースの要件に強制することは、単に正しくありません。
PerformanceDBA、

2
興味深い答え。受け入れられた回答でPerson-Employeeの例をモデル化することをどのように提案しますか
セブンフォース2014年

2
@ sevenforce-DBの設計は、実際には与えられていないシステムの要件に依存しています。決定するのに十分な情報が提供されていません。多くの場合、「型ごとのテーブル」の設計に似たものが、たやすく従わなければ適切な場合があります。たとえば、start-dateはおそらくEmployeeオブジェクトに適したプロパティですが、データベースでは実際にはEmploymentテーブルのフィールドである必要があります。これは、人が複数の開始日で複数回雇用される可能性があるためです。これはオブジェクト(最新のものを使用する)には関係ありませんが、データベースでは重要です。
Jeffrey L Whitledge 2014年

2
確かに、私の質問は主に継承をモデル化する方法についてでした。十分に明確ではなかったため申し訳ありません。ありがとう。おっしゃったように、Employmentすべての雇用とその開始日を収集するテーブルがあるはずです。では、の現在の雇用開始日を知ることEmployerが重要である場合View、それはの適切なユースケースになる可能性があります。(注:ニックネームの直後に「-」が付いているため、コメントで通知が届かなかったようです)
sevenforce

5
これは答えの本当の逸品です。実際に理解するには時間がかかり、適切に実行するにはいくつかの練習が必要ですが、リレーショナルデータベースの設計に関する私の思考プロセスにはすでに影響を与えています。
MarioDS

9

短い答え:そうではありません。

オブジェクトをシリアル化する必要がある場合は、ORMを使用するか、activerecordやprevaylenceなどを使用します。

データを格納する必要がある場合は、オブジェクトの設計の影響を受けないように、リレーショナルな方法で格納します(格納する内容に注意し、Jeffrey L Whitledgeが今言ったことに注意を払います)。


3
+1データベースで継承をモデル化しようとすると、優れたリレーショナルリソースが無駄になります。
Daniel Spiewak、2008年

7

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;

4

DBで設定できる継承には、主に2つのタイプがあります。エンティティごとのテーブルと階層ごとのテーブルです。

エンティティごとのテーブルは、すべての子クラスのプロパティを共有する基本エンティティテーブルがある場所です。次に、子クラスごとに、そのクラスに適用可能なプロパティのみを持つ別のテーブルを作成します。それらはPKによって1:1でリンクされています

代替テキスト

階層ごとのテーブルは、すべてのクラスがテーブルを共有する場所であり、オプションのプロパティはnullにできます。それらは、レコードが現在保持しているタイプを示す数値である弁別子フィールドでもあります。

代替テキスト SessionTypeIDは識別子です

結合が必要ないため(識別値のみ)、階層ごとのターゲットの方がクエリが高速ですが、エンティティごとのターゲットは、何かのタイプを検出し、すべてのデータを取得するために複雑な結合を実行する必要があります。

編集:ここに表示する画像は、現在取り組んでいるプロジェクトのスクリーンショットです。アセットイメージは完全ではないため、何もありませんが、主にセットアップ方法を示すためであり、テーブル内に何を配置するかではありません。あれは君次第だ ;)。セッションテーブルは仮想コラボレーションセッション情報を保持し、関与するコラボレーションのタイプに応じて、いくつかのタイプのセッションになる可能性があります。


また、Target per Concreteクラスは継承を実際にうまくモデル化しないと考えたので、表示しませんでした。
マットラント、2008年

イラストの出典を追加できますか?
08年

回答の最後に話している画像はどこにありますか?
Musa

1

データベースを正規化すると、実際に継承が反映されます。パフォーマンスが低下する可能性がありますが、それが正規化の方法です。あなたはおそらくバランスを見つけるために良い常識を使わなければならないでしょう。


2
なぜ人々はデータベースの正規化がパフォーマンスを低下させると信じているのですか?人々はまた、DRY原則がコードのパフォーマンスを低下させると思いますか?この誤解はどこから来たのですか?
スティーブンA.ロウ

1
おそらく、非正規化によってパフォーマンスが向上するため、正規化するとパフォーマンスが低下する可能性があります。私はそれに同意することはできませんが、それはおそらくそれが起こった方法です。
Matthew Scharley、2008年

2
最初は、正規化によるパフォーマンスへの影響はわずかですが、時間の経過とともに行数が増えるにつれて、効率的なJOINがかさばるテーブルよりも優れたパフォーマンスを発揮し始めます。もちろん、正規化には他にも大きな利点があります-一貫性や冗長性の欠如など
Rob

1

同様のスレッドの答えを繰り返す

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テーブルが必要になります

やり過ぎのように見えますが、それはすべて、それを何に使用するかによって異なります。


その議論は、継承をモデル化することではなく、すべてのテーブルにいくつかの列を追加することで終わりました。そのディスカッションのタイトルは、質問とディスカッションの性質をよりよく反映するように変更する必要があると思います。
Mienでさえ、

1

SQL ALchemy(Python ORM)を使用すると、2種類の継承を行うことができます。

私が経験したのは、1つのテーブルを使用し、判別式の列を持っていることです。たとえば、羊データベース(冗談ではありません!)はすべての羊を1つのテーブルに格納し、ラムと羊はそのテーブルの性別列を使用して処理されました。

したがって、すべての羊を照会して、すべての羊を取得できます。または、ラムのみでクエリを実行して、ラムのみを取得することもできます。また、Ram(つまり、羊の飼い主)にしかできないリレーションシップなどを作成することもできます。


1

一部のデータベースエンジンは、Postgresのようなネイティブの継承メカニズムをすでに提供していることに注意してください。ドキュメントをご覧ください。

たとえば、次のように、上記の応答で説明されているPerson / Employeeシステムをクエリします。

  / *これは、すべての人または従業員の名を示します* /
  個人から名を選択; 

  / *これは全従業員の開始日のみを示します* /
  SELECT startdate FROM Employee;

それがあなたのデータベースの選択であるので、あなたは特にスマートである必要はありません!

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.