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


236

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

オブジェクトのファミリの詳細を格納する必要があるアプリケーションを考えてみてください。これらのオブジェクトは、いくつかの属性を共有しますが、他の多くの属性は一般的ではありません。たとえば、商業保険パッケージには、同じ保険証券レコード内の負債、モーター、財産、および補償の補償が含まれる場合があります。

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

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

  1. Policyテーブルを作成し、次にSectionsテーブルを作成します。可能なすべてのバリエーションについて、すべてのフィールドが必須であり、ほとんどがnullになります。

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

特に、すべてのセクションにまたがるクエリを記述する必要があるため、これらの方法はどちらも不十分であるように見えます。

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


回答:


430

@Bill Karwinは、SQL Entity-Attribute-Valueアンチパターンのソリューションを提案するときに、SQL Antipatternsブックで3つの継承モデルについて説明しています。これは簡単な概要です:

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

最初のオプションのように単一のテーブルを使用することは、おそらく最も単純な設計です。すでに述べたように、サブタイプ固有の多くの属性には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、必須であるべきサブタイプの属性を強制することもできません。アプリケーションでこれを処理する必要がありますが、これは一般的に理想的ではありません。

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

継承に取り組むもう1つの方法は、サブタイプごとに新しいテーブルを作成し、各テーブルのすべての共通属性を繰り返すことです。例えば:

--// 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ではありません。

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

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

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ポリシーの自動インクリメントの管理は、サブタイプテーブルごとに個別に生成するのではなく、ベーステーブルで処理できるため、より簡単になります。

  • サブタイプに関係なくすべてのポリシーを検索することが非常に簡単になりました。sはUNION必要ありませんSELECT * FROM policies

クラステーブルアプローチは、ほとんどの状況で最も適切であると考えています。


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


97
私もこのデザインを使用していますが、欠点については触れていません。具体的には、1)タイプは必要ないということです。trueですが、すべてのサブタイプテーブルを調べて一致を見つけない限り、行の実際のタイプを特定できません。2)マスターテーブルとサブタイプテーブルの同期を保つのは困難です(たとえば、マスターテーブルではなくサブタイプテーブルの行を削除できます)。3)マスター行ごとに複数のサブタイプを持つことができます。トリガーを使用して1を回避していますが、2と3は非常に難しい問題です。構成をモデル化する場合、実際には3は問題ではありませんが、厳密な継承のためのものです。

19
@Tiboのコメントの+1、それは重大な問題です。クラステーブルの継承は、実際には正規化されていないスキーマを生成します。コンクリートテーブルの継承はそうではありませんが、コンクリートテーブルの継承がDRYを妨げるという議論には同意しません。SQLにはメタプログラミング機能がないため、DRYの妨げになります。解決策は、SQLを直接記述するのではなく、データベースツールキットを使用して(または独自に記述して)重い作業を行うことです(実際には、DBインターフェイス言語にすぎません)。結局のところ、エンタープライズアプリケーションをアセンブリで作成する必要もありません。
Jo So

18
@Tibo、ポイント3については、ここで説明されているアプローチを使用できます。sqlteam.com / article / 、「1対1の制約のモデリング」セクションを確認してください。
Andrew

4
@DanielVassalloまず、すばらしい答えに感謝します。1人の人がpolicyIdを持っているかどうか疑って、policy_motorかpolicy_propertyかを知る方法を教えてください。1つの方法は、すべてのサブテーブルでpolicyIdを検索することですが、これは悪い方法ではないでしょう。正しいアプローチは何でしょうか?
ThomasBecker 2015

11
私はあなたの3番目のオプションが本当に好きです。しかし、SELECTがどのように機能するのか混乱しています。SELECT * FROMポリシーの場合、ポリシーIDが返されますが、ポリシーがどのサブタイプテーブルに属しているかはわかりません。ポリシーの詳細をすべて取得するために、すべてのサブタイプを使用してJOINを実行する必要はありませんか?
Adam

14

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

どちらが最適かは、主にフィールドの数とSQLの記述方法によって決まります。それらはすべて機能します。ほんの数フィールドしかない場合は、おそらく#1を使用します。「たくさんの」フィールドがあると、私は#2または#3に傾くでしょう。


+1:3番目のオプションは継承モデルに最も近く、最も正規化されたIMO
RedFilter

あなたのオプション#3は、私がオプション#2で本当に意味したものです。多くのフィールドがあり、一部のセクションにも子エンティティがあります。
スティーブジョーンズ

9

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

ポリシー

  • POLICY_ID(主キー)

負債

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

プロパティ

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

...など、ポリシーの各セクションに関連付けられているさまざまな属性があると思います。それ以外の場合は、単一があるかもしれないSECTIONSテーブルとに加えてpolicy_id、そこだろうsection_type_code...

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

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

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


ええ、私は正規化を取得します;-)このような複雑な構造では、いくつかのセクションは単純で、一部は独自の複雑なサブ構造を持っているため、ORMが機能する可能性は低いですが、すばらしいです。
スティーブジョーンズ

6

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

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

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


1
それは興味深いアイデアです。私はSQL ServerでJSONをまだ使用していませんが、他の多くの場所で使用しています。ヘッドアップをありがとう。
スティーブジョーンズ

5

それを行う別の方法は、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);

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


他のDBには対応していますINHERITSほかにPostgreSQLのMySQLの例?
giannis christofakis 2016年

1
@giannischristofakis:MySQLは単なるリレーショナルデータベースですが、Postgresはオブジェクトリレーショナルデータベースです。したがって、MySQLはこれをサポートしていません。実際、この種の継承をサポートする現在のDBMSはPostgresだけだと思います。
a_horse_with_no_name 2016

2
@ marco-paulo-ollivier、OPの質問はSQL Serverに関するものなので、Postgresでのみ機能するソリューションを提供する理由がわかりません。明らかに、問題に対処していません。
mapto

@maptoこの質問は、「データベースでOOスタイルの継承をどのように行うのか」という疑似ターゲットになりました。それがもともとSQLサーバーについてだったということは、おそらく今は無関係です
Caius Jard

0

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

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

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


ポリシー全体を一度に表示するには情報が多すぎるため、レコード全体を取得する必要はありません。他のプロジェクトでは2008のスパースを使用しましたが、2005年だと思います。
スティーブジョーンズ

「統一セクションテーブル」という用語はどこから来たのですか?グーグルはそれについてほとんど結果を示していません、そしてここに十分に混乱する用語があります。
Stephan-v

-1

または、豊富なデータ構造とネストをネイティブにサポートするドキュメントデータベース(MongoDBなど)の使用を検討してください。


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