SQL Serverデータベースで複雑な構造を表現する方法を考えています。
オブジェクトのファミリーの詳細を保存する必要があるアプリケーションを考えてみましょう。これらのオブジェクトは、いくつかの属性を共有しますが、他の多くは一般的ではありません。たとえば、商業保険パッケージには、同じ保険契約記録内の賠償責任、自動車、財産、および補償が含まれる場合があります。
セクションのコレクションでポリシーを作成できるため、C#などでこれを実装するのは簡単です。セクションはさまざまなタイプのカバーに必要に応じて継承されます。ただし、リレーショナルデータベースではこれが簡単にできないようです。
主に2つの選択肢があることがわかります。
ポリシーテーブルを作成し、次にセクションテーブルを作成します。すべての可能なバリエーションについて、必要なすべてのフィールドを使用します。ほとんどのフィールドはnullです。
カバーの種類ごとに1つずつ、ポリシーテーブルと多数のセクションテーブルを作成します。
特に、多数の結合またはヌルチェックを含むすべてのセクションにわたってクエリを記述する必要があるため、これらの選択肢はどちらも不十分なようです。
このシナリオのベストプラクティスは何ですか?
@ 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 ではありません。
サブタイプに関係なくすべてのポリシーを検索することは難しくなり、UNION
sの束が必要になります。
これは、タイプに関係なくすべてのポリシーを照会する方法です。
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
の管理は、サブタイプテーブルごとに個別に生成するのではなく、ベーステーブルで処理できるため、より簡単になります。
サブタイプに関係なくすべてのポリシーを検索することが非常に簡単になりました:UNION
sは不要-SELECT * FROM policies
だけです。
ほとんどの場合、クラステーブルアプローチが最も適していると考えています。
これら3つのモデルの名前は、 Martin Fowler's book Patterns of Enterprise Application Architecture に由来しています。
3番目のオプションは、「ポリシー」テーブルを作成してから、セクションのタイプ全体で共通するすべてのフィールドを保存する「セクション」メインテーブルを作成することです。次に、共通していないフィールドのみを含むセクションのタイプごとに他のテーブルを作成します。
どちらが最適かは主に、フィールドの数とSQLの書き方によって決まります。彼らはすべて動作します。いくつかのフィールドしかない場合は、おそらく#1に進みます。 「たくさん」のフィールドがあると、私は#2または#3に傾くでしょう。
提供された情報を使用して、データベースを次のようにモデル化します。
...など、ポリシーの各セクションに異なる属性が関連付けられると予想されるためです。そうでなければ、単一のSECTIONS
テーブルがあり、policy_id
に加えて、section_type_code
...があります。
いずれにしても、これによりポリシーごとにオプションのセクションをサポートできます...
このアプローチについてあなたが不満足だと思うことは理解できません-これは、参照整合性を維持し、データを複製しないでデータを保存する方法です。用語は「正規化」です...
SQLはSETベースであるため、手続き型/ OOプログラミングの概念とは相反し、1つのレルムから別のレルムに移行するためのコードが必要です。 ORMはよく考慮されますが、大量の複雑なシステムではうまく機能しません。
別の方法は、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 文字列を持つ単一の列を追加できます。
継承を管理するためにこの設計をテストしましたが、関連するアプリケーションで使用できる柔軟性に非常に満足しています。
すべてのセクションでポリシー全体を効率的に取得するために、方法#1(統一されたセクションテーブル)に傾いています(システムが多くのことを行うと想定しています)。
さらに、使用しているSQL Serverのバージョンはわかりませんが、2008 + Sparse Columns では、列の値の多くがNULLになる状況でパフォーマンスを最適化できます。
最終的に、ポリシーセクションの「類似度」を決定する必要があります。それらが大幅に異なる場合を除き、より正規化されたソリューションは、それが価値があるよりも厄介かもしれないと思います...しかし、あなただけがその呼び出しを行うことができます。 :)