293

I'm thinking about how to represent a complex structure in a SQL Server database.

Consider an application that needs to store details of a family of objects, which share some attributes, but have many others not common. For example, a commercial insurance package may include liability, motor, property and indemnity cover within the same policy record.

It is trivial to implement this in C#, etc, as you can create a Policy with a collection of Sections, where Section is inherited as required for the various types of cover. However, relational databases don't seem to allow this easily.

I can see that there are two main choices:

  1. Create a Policy table, then a Sections table, with all the fields required, for all possible variations, most of which would be null.

  2. Create a Policy table and numerous Section tables, one for each kind of cover.

Both of these alternatives seem unsatisfactory, especially as it is necessary to write queries across all Sections, which would involve numerous joins, or numerous null-checks.

What is the best practice for this scenario?

4

7 に答える 7

520

@Bill Karwinは、SQL Entity-Attribute-Valueアンチパターンのソリューションを提案する際に、SQLアンチパターンの本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また、必須であるはずのサブタイプの属性を強制することもできません。これはアプリケーションで処理する必要がありますが、一般的には理想的ではありません。

具体的なテーブル継承:

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

--// 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の管理が容易になります。これは、各サブタイプテーブルがポリシーを個別に生成するのではなく、ベーステーブルで処理できるためです。

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

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


これらの3つのモデルの名前は、MartinFowlerの著書Patternsof EnterpriseApplicationArchitectureに由来しています。

于 2010-08-26T20:59:23.653 に答える
18

The 3rd option is to create a "Policy" table, then a "SectionsMain" table that stores all of the fields that are in common across the types of sections. Then create other tables for each type of section that only contain the fields that are not in common.

Deciding which is best depends mostly on how many fields you have and how you want to write your SQL. They would all work. If you have just a few fields then I would probably go with #1. With "lots" of fields I would lean towards #2 or #3.

于 2010-08-26T20:15:48.953 に答える
10

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

ポリシー

  • POLICY_ID(主キー)

負債

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

プロパティ

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

...など、ポリシーの各セクションに関連付けられたさまざまな属性があると予想されるためです。それ以外の場合は、単一のSECTIONSテーブルが存在する可能性があり、に加えてpolicy_idsection_type_code...

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

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

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

于 2010-08-26T20:22:50.030 に答える
10

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

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

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

于 2017-09-01T12:17:09.037 に答える
4

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

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

于 2013-12-04T16:10:56.387 に答える
0

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

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

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

于 2010-08-26T20:22:05.930 に答える