web-dev-qa-db-ja.com

データベースで継承をどのように表現できますか?

SQL Serverデータベースで複雑な構造を表現する方法を考えています。

オブジェクトのファミリーの詳細を保存する必要があるアプリケーションを考えてみましょう。これらのオブジェクトは、いくつかの属性を共有しますが、他の多くは一般的ではありません。たとえば、商業保険パッケージには、同じ保険契約記録内の賠償責任、自動車、財産、および補償が含まれる場合があります。

セクションのコレクションでポリシーを作成できるため、C#などでこれを実装するのは簡単です。セクションはさまざまなタイプのカバーに必要に応じて継承されます。ただし、リレーショナルデータベースではこれが簡単にできないようです。

主に2つの選択肢があることがわかります。

  1. ポリシーテーブルを作成し、次にセクションテーブルを作成します。すべての可能なバリエーションについて、必要なすべてのフィールドを使用します。ほとんどのフィールドはnullです。

  2. カバーの種類ごとに1つずつ、ポリシーテーブルと多数のセクションテーブルを作成します。

特に、多数の結合またはヌルチェックを含むすべてのセクションにわたってクエリを記述する必要があるため、これらの選択肢はどちらも不十分なようです。

このシナリオのベストプラクティスは何ですか?

199
Steve Jones

@ Bill Karwin は、SQLの解決策を提案する際に SQL Antipatterns 本で3つの継承モデルを説明しています Entity-Attribute-Value antipattern。これは簡単な概要です:

単一のテーブル継承(別名テーブルごとの階層継承):

最初のオプションのように単一のテーブルを使用するのがおそらく最も簡単な設計です。前述のように、サブタイプ固有の多くの属性には、これらの属性が適用されない行のNULL値を指定する必要があります。このモデルでは、1つのポリシーテーブルがあり、次のようになります。

+------+---------------------+----------+----------------+------------------+
| id   | date_issued         | type     | vehicle_reg_no | property_address |
+------+---------------------+----------+----------------+------------------+
|    1 | 2010-08-20 12:00:00 | MOTOR    | 01-A-04004     | NULL             |
|    2 | 2010-08-20 13:00:00 | MOTOR    | 02-B-01010     | NULL             |
|    3 | 2010-08-20 14:00:00 | PROPERTY | NULL           | Oxford Street    |
|    4 | 2010-08-20 15:00:00 | MOTOR    | 03-C-02020     | NULL             |
+------+---------------------+----------+----------------+------------------+

\------ COMMON FIELDS -------/          \----- SUBTYPE SPECIFIC FIELDS -----/

設計をシンプルに保つことはプラスですが、このアプローチの主な問題は次のとおりです。

  • 新しいサブタイプの追加に関しては、これらの新しいオブジェクトを記述する属性に対応するためにテーブルを変更する必要があります。これは、サブタイプが多数ある場合、またはサブタイプを定期的に追加する予定がある場合、すぐに問題になります。

  • データベースは、どの属性がどのサブタイプに属するかを定義するメタデータがないため、どの属性が適用され、どの属性が適用されないかを強制できません。

  • 必須のサブタイプの属性にNOT NULLを強制することもできません。アプリケーションでこれを処理する必要がありますが、一般的には理想的ではありません。

具体的なテーブルの継承:

継承に取り組む別のアプローチは、各サブタイプの新しいテーブルを作成し、各テーブルのすべての共通属性を繰り返すことです。例えば:

--// Table: policies_motor
+------+---------------------+----------------+
| id   | date_issued         | vehicle_reg_no |
+------+---------------------+----------------+
|    1 | 2010-08-20 12:00:00 | 01-A-04004     |
|    2 | 2010-08-20 13:00:00 | 02-B-01010     |
|    3 | 2010-08-20 15:00:00 | 03-C-02020     |
+------+---------------------+----------------+

--// Table: policies_property    
+------+---------------------+------------------+
| id   | date_issued         | property_address |
+------+---------------------+------------------+
|    1 | 2010-08-20 14:00:00 | Oxford Street    |   
+------+---------------------+------------------+

この設計は、基本的に単一テーブル方式で特定された問題を解決します。

  • NOT NULLを使用して必須属性を適用できるようになりました。

  • 新しいサブタイプを追加するには、既存のテーブルに列を追加する代わりに、新しいテーブルを追加する必要があります。

  • プロパティポリシーのvehicle_reg_noフィールドなど、特定のサブタイプに不適切な属性が設定されるリスクもありません。

  • 単一テーブルのメソッドのように、type属性は必要ありません。タイプは、メタデータによって定義されるようになりました:テーブル名。

ただし、このモデルにはいくつかの欠点もあります。

  • 共通の属性はサブタイプ固有の属性と混合されており、それらを識別する簡単な方法はありません。データベースも認識しません。

  • テーブルを定義するとき、各サブタイプテーブルに共通の属性を繰り返す必要があります。それは間違いなく DRY ではありません。

  • サブタイプに関係なくすべてのポリシーを検索することは難しくなり、UNIONsの束が必要になります。

これは、タイプに関係なくすべてのポリシーを照会する方法です。

SELECT     date_issued, other_common_fields, 'MOTOR' AS type
FROM       policies_motor
UNION ALL
SELECT     date_issued, other_common_fields, 'PROPERTY' AS type
FROM       policies_property;

新しいサブタイプを追加するには、各サブタイプにUNION ALLを追加して上記のクエリを変更する必要があることに注意してください。この操作を忘れると、アプリケーションでバグが発生しやすくなります。

クラステーブル継承(別名タイプごとのテーブル継承):

これは @ Davidが他の回答で言及している であるソリューションです。基本クラス用に単一のテーブルを作成します。これには、すべての共通属性が含まれます。次に、サブタイプごとに特定のテーブルを作成します。そのサブキーは、ベーステーブルに対して 外部キー としても機能します。例:

CREATE TABLE policies (
   policy_id          int,
   date_issued        datetime,

   -- // other common attributes ...
);

CREATE TABLE policy_motor (
    policy_id         int,
    vehicle_reg_no    varchar(20),

   -- // other attributes specific to motor insurance ...

   FOREIGN KEY (policy_id) REFERENCES policies (policy_id)
);

CREATE TABLE policy_property (
    policy_id         int,
    property_address  varchar(20),

   -- // other attributes specific to property insurance ...

   FOREIGN KEY (policy_id) REFERENCES policies (policy_id)
);

このソリューションは、他の2つの設計で特定された問題を解決します。

  • 必須属性はNOT NULLで強制できます。

  • 新しいサブタイプを追加するには、既存のテーブルに列を追加する代わりに、新しいテーブルを追加する必要があります。

  • 特定のサブタイプに不適切な属性が設定されるリスクはありません。

  • type属性は不要です。

  • これで、共通属性はサブタイプ固有の属性と混合されなくなりました。

  • ついに乾いたままでいられます。テーブルを作成するときに、各サブタイプテーブルに共通の属性を繰り返す必要はありません。

  • ポリシーの自動増分idの管理は、サブタイプテーブルごとに個別に生成するのではなく、ベーステーブルで処理できるため、より簡単になります。

  • サブタイプに関係なくすべてのポリシーを検索することが非常に簡単になりました:UNIONsは不要-SELECT * FROM policiesだけです。

ほとんどの場合、クラステーブルアプローチが最も適していると考えています。


これら3つのモデルの名前は、 Martin Fowler's book Patterns of Enterprise Application Architecture に由来しています。

386
Daniel Vassallo

3番目のオプションは、「ポリシー」テーブルを作成してから、セクションのタイプ全体で共通するすべてのフィールドを保存する「セクション」メインテーブルを作成することです。次に、共通していないフィールドのみを含むセクションのタイプごとに他のテーブルを作成します。

どちらが最適かは主に、フィールドの数とSQLの書き方によって決まります。彼らはすべて動作します。いくつかのフィールドしかない場合は、おそらく#1に進みます。 「たくさん」のフィールドがあると、私は#2または#3に傾くでしょう。

12
David

提供された情報を使用して、データベースを次のようにモデル化します。

ポリシー

  • POLICY_ID(主キー)

負債

  • LIABILITY_ID(主キー)
  • POLICY_ID(外部キー)

特性

  • PROPERTY_ID(主キー)
  • POLICY_ID(外部キー)

...など、ポリシーの各セクションに異なる属性が関連付けられると予想されるためです。そうでなければ、単一のSECTIONSテーブルがあり、policy_idに加えて、section_type_code...があります。

いずれにしても、これによりポリシーごとにオプションのセクションをサポートできます...

このアプローチについてあなたが不満足だと思うことは理解できません-これは、参照整合性を維持し、データを複製しないでデータを保存する方法です。用語は「正規化」です...

SQLはSETベースであるため、手続き型/ OOプログラミングの概念とは相反し、1つのレルムから別のレルムに移行するためのコードが必要です。 ORMはよく考慮されますが、大量の複雑なシステムではうまく機能しません。

9
OMG Ponies

別の方法は、INHERITSコンポーネントを使用することです。例えば:

CREATE TABLE person (
    id int ,
    name varchar(20),
    CONSTRAINT pessoa_pkey PRIMARY KEY (id)
);

CREATE TABLE natural_person (
    social_security_number varchar(11),
    CONSTRAINT pessoaf_pkey PRIMARY KEY (id)
) INHERITS (person);


CREATE TABLE juridical_person (
    tin_number varchar(14),
    CONSTRAINT pessoaj_pkey PRIMARY KEY (id)
) INHERITS (person);

したがって、テーブル間の継承を定義することができます。

さらに、Daniel Vassalloソリューションでは、SQL Server 2016を使用する場合、パフォーマンスを大幅に低下させることなく使用した別のソリューションがあります。

共通フィールドのみを持つテーブルのみを作成し、すべてのサブタイプ固有フィールドを含む JSON 文字列を持つ単一の列を追加できます。

継承を管理するためにこの設計をテストしましたが、関連するアプリケーションで使用できる柔軟性に非常に満足しています。

2
overcomer

すべてのセクションでポリシー全体を効率的に取得するために、方法#1(統一されたセクションテーブル)に傾いています(システムが多くのことを行うと想定しています)。

さらに、使用しているSQL Serverのバージョンはわかりませんが、2008 + Sparse Columns では、列の値の多くがNULLになる状況でパフォーマンスを最適化できます。

最終的に、ポリシーセクションの「類似度」を決定する必要があります。それらが大幅に異なる場合を除き、より正規化されたソリューションは、それが価値があるよりも厄介かもしれないと思います...しかし、あなただけがその呼び出しを行うことができます。 :)

0
Dan J