相互に排他的なサブクラスを持つタイプ/サブタイプデザインパターンでのサブタイプのサブタイプの実装


20

前書き

この質問が将来の読者に役立つように、一般的なデータモデルを使用して、直面している問題を説明します。

我々のデータモデルは、としてラベル付けされなければならない3つの事業体で構成ABおよびC。物事をシンプルに保つために、それらの属性はすべてintタイプになります。

エンティティにAは次の属性があります:DEおよびX;

エンティティにBは次の属性があります:DEおよびY;

エンティティにCは次の属性がDありZます。

すべてのエンティティが共通の属性を共有Dしているため、タイプ/サブタイプデザインを適用することにしました。

重要:エンティティは相互に排他的です!これは、エンティティがAまたはBまたはCであることを意味します。

問題:

エンティティABは、さらに別の一般的な属性Eがありますが、この属性はエンティティに存在しませんC

質問:

可能であれば、上記の特性を使用して設計をさらに最適化したいと思います。

正直に言うと、これをどのように行うのか、どこから試すのかわからないので、この投稿です。

回答:


6

この質問が「相互排他的なサブクラスのタイプ/サブタイプデザインパターンの実装は正しいですか?」の続きである限り、、これ自体はの続きですリレーショナルテーブルに変数エンティティを変換する方法がわからない:、私が求めるだろうを正確にあなたが最適化しようとしていますか?ストレージ?オブジェクトモデル?クエリの複雑さ?クエリのパフォーマンス?すべてのアスペクトを同時に最適化することはできないため、あるアスペクトと別のアスペクトを最適化する際にはトレードオフがあります。

Remusの次の点に完全に同意します。

  • 各アプローチには長所と短所があります(つまり、常に存在する「依存する」要因)。
  • 最初の優先事項は、データモデルの効率です(非効率的なデータモデルは、クリーンで効率的なアプリコードでは修正できません)

そうは言っても、あなたが直面する選択は、次のいずれかであり、最小正規化から最大正規化の順に並べられています。

  • 財産の促進 E基本型テーブルへの
  • 複数のサブタイプのテーブルに保持する
  • Eと同じレベルにある新しい中間サブクラステーブルに完全に正規化するCこと。AそれBは、(@ MDCCL's answer

各オプションを見てみましょう:

プロパティを移動 Eをベースタイプテーブルにする

プロ

  • その必要性クエリのクエリの複雑さを低減しEなくXYまたはZ
  • 、、またはを必要としEないがX、必要としないクエリでは潜在的に効率的YZ無参加することにより、(特に集計クエリ)。
  • インデックスを作成する可能性(D, E)(もしそうであれば、そのような条件が許可されている場合は、潜在的(D, E)にEntityType <>のフィルターインデックスC

短所

  • EとしてマークできませんNOT NULL
  • 必要性の余分なCHECK CONSTRAINTベース型のテーブルの上にあることを確実にするE IS NULLときEntityType = C(これは大きな問題ではありませんが)
  • EntityType =の場合、なぜそうするE必要があるかについてデータモデルのユーザーを教育する必要がありNULL、完全に無視する必要さえありますC
  • わずかに少ない効率的な場合E、固定長型であり、そして行の大部分は、EntityTypeのためのものC(すなわち、使用していないE、したがってそれがNULL)、そしていずれかを使用しませんSPARSE、列オプションまたはクラスター化インデックスのデータ圧縮
  • 基本タイプのテーブルにE存在するとE各行のサイズが大きくなり、データページに収まる行数が減少するため、必要のないクエリでは潜在的に効率が低下します。ただし、これは、の正確なデータ型E、FILLFACTOR、ベースタイプテーブルに含まれる行数などに大きく依存します。

E各サブタイプテーブルにプロパティを保持する

プロ

  • よりクリーンなデータモデル(つまりE、「実際には存在しない」ためにベースタイプテーブルの列を使用しない理由について他の人を教育する必要はありません)
  • おそらくオブジェクトモデルにより似ている
  • NOT NULLこれがエンティティの必須プロパティであるかのように列をマークできます
  • 余分の必要ないんCHECK CONSTRAINTことを保証する基本型テーブル上のE IS NULLときEntityType = C(これは巨大な利益ではありませんが)

短所

  • このプロパティを取得するには、サブタイプテーブルにJOINが必要です
  • +のE行数とは対照的に、A+の行数に応じて、JOINにより、必要なときに潜在的にわずかに効率が低下しBますC
  • 少し難しい/複雑な実体でのみ取り扱う業務用AB(とない C同じ「タイプ」であることなど)。もちろん、あなたの可能性ないビューを経由してこの抽象UNION ALLの間SELECTのための結合されたテーブルのASELECTのための結合されたテーブルのB。これにより、SELECTクエリの複雑さが軽減されますがINSERTUPDATEクエリに。
  • 特定のクエリとそれらが実行される頻度によっては、(D, E)インデックスを一緒にインデックス化できないため、インデックスをオンにすることで1つ以上の頻繁に使用されるクエリが本当に役立つ場合、非効率になる可能性があります。

E基本クラスとA&の間の中間テーブルに正規化するB

@MDCCLの答えは、状況に応じて実行可能な代替手段として好きです。以下は、そのアプローチに対する厳密な批判を意味するものではありません。既に提案した2つのオプションと同じコンテキストで。これにより、完全正規化と部分正規化の現在のアプローチとの相対的な違いとして私が見ているものを明確にしやすくなります。

プロ

  • データモデルは完全に正規化されています(RDBMSが行うように設計されているため、これに本質的に問題はありません)
  • Aおよびを必要とするクエリのクエリの複雑さを軽減しますBが、そうではありませんC(つまり、を介して結合される2つのクエリは不要ですUNION ALL

短所

  • わずかに多くのスペースが使用されます(BarテーブルはIDを複製し、新しい列があります。BarTypeCode)[無視でき注意すべき点]
  • 追加として、クエリの複雑さのわずかな増加は、JOINに到達するために必要とされるか、AまたはB
  • トランザクションはベースクラスのテーブルで少し長く開いたままになるので、ロックするための表面積が増加しますINSERTDELETE外部キーをマークすることで暗黙的に処理できますON CASCADE DELETE)(すなわちFoo
  • 実際のタイプの直接的な知識- AまたはB-基底クラスのテーブル内、Foo。または、次のBrいずれかのタイプのみを知っています。 AB

    つまり、一般的なベース情報に対してクエリを実行する必要があるが、エンティティタイプで分類するか、1つ以上のエンティティタイプを除外する必要がある場合、ベースクラステーブルには十分な情報がありません。テーブル。これにより、列のインデックス作成の効率も低下します。LEFT JOINBarFooTypeCode

  • ABvs と対話するための一貫したアプローチはありませんC

    つまり、各エンティティが完全なエンティティを取得するためのJOINが1つだけであるようにベースクラステーブルに直接関連している場合、誰もがデータモデルの操作に関してより迅速かつ簡単に慣れることができます。クエリ/ストアドプロシージャに対する一般的なアプローチにより、開発が迅速になり、バグが発生しにくくなります。また、一貫したアプローチにより、将来、新しいサブタイプをより迅速かつ簡単に追加できます。

  • 時間の経過とともに変化するビジネスルールへの適応性が低い可能性があります。

    意味、物事は常に変化し、Eすべてのサブタイプに共通になる場合、基本クラスのテーブルに移動するのはかなり簡単です。また、エンティティの性質の変更によって価値のある変更が行われた場合、共通のプロパティをサブタイプに移動するのも簡単です。サブタイプを2つのサブタイプに分割する(別のSubTypeID値を作成する)か、2つ以上のサブタイプを1つに結合するのは簡単です。逆に、E後ですべてのサブタイプの共通プロパティになった場合はどうなりますか?その場合、Barテーブルの中間層は無意味になり、追加された複雑さはそれだけの価値はありません。もちろん、そのような変化が5年後か10年後かを知ることは不可能なので、Barテーブルは必ずしも、またはその可能性は非常に低く、悪い考えです(だからこそ、「適応性が低い可能性がある」と言ったのです)。これらは考慮すべき点にすぎません。それはどちらの方向のギャンブルでもあります。

  • 不適切な可能性のあるグループ化:

    理由だけで、意味Eプロパティはエンティティタイプ間で共有されているABことを意味するわけではないAし、B する必要があり、一緒にグループ化すること。物事が同じように見える(つまり同じプロパティ)からといって、それらが同じであるという意味ではありません。

概要

非正規化するかどうか、いつ非正規化するかを決定するのと同じように、この特定の状況に最適にアプローチする方法は、データモデルの使用に関する次の側面を考慮し、メリットがコストを上回ることを確認することに依存します。

  • 各EntityTypeに対して何行持つか(平均以上の成長を想定して、少なくとも5年先を見ます)
  • これらの各テーブル(ベースタイプとサブタイプ)は5年間で何GBになりますか?
  • 特定のデータ型はプロパティです E
  • プロパティは1つだけですか、それともいくつかのプロパティ、または複数のプロパティがありますか
  • 必要なクエリとそのE実行頻度
  • 不要なクエリと不要なクエリの実行E頻度

デフォルトEでは、少なくとも「クリーナー」であるため、個別のサブタイプテーブルに保存する傾向があると思います。E基本型テーブルIFへの移動を検討します。ほとんどの行はEntityTypeの対象ではありませんCそして、行の数は、少なくとも数百万人でした。そして、Eは、(D, E)頻繁に実行する、および/またはインデックスが全体的なリソース使用率を減らすか、少なくとも防ぐために十分なシステムリソースを必要とする必要があるクエリおよび/またはインデックスの恩恵を受けるクエリを実行しません許容レベルを超えるリソース消費の急増、または過度のブロッキングやデッドロックの増加を引き起こすほど長く続く。


更新

OP はこの回答について次のようにコメントしました。

私の雇用主はビジネスロジックを変更し、Eを完全に削除しました!

この変更は特に重要です。なぜなら、上記の「E基本クラスとA&の間の中間テーブルへの正規化」セクションの「CONs」サブセクションで予測されることだからですB(6番目の箇条書き)。具体的な問題は、そのような変更が発生した場合にデータモデルをリファクタリングするのがどれだけ簡単/難しいかということです(そして常にそうします)。一部のデータモデルはリファクタリング/変更できるので、理想から始めましょう。しかし、技術レベルでは何でもリファクタリングできることは事実ですが、状況の現実は規模の問題です。

リソースは無限ではなく、CPU /ディスク/ RAMだけでなく、開発リソース、つまり時間とお金も無限です。これらのリソースは非常に限られているため、企業は常にプロジェクトの優先順位を設定しています。そして、(少なくとも私の経験では)効率を上げるプロジェクト(システムパフォーマンスと開発の高速化/バグの削減の両方)が、機能を向上させるプロジェクトよりも優先されることがよくあります。リファクタリングプロジェクトの長期的なメリットを理解しているので、技術者にとってはイライラしますが、技術のあまりないビジネス人が新しい機能と新しい機能の直接的な関係を簡単に理解できるのはビジネスの性質です収益。要約すると、「後で修正するために戻ってきます」== "

それを念頭に置いて、データのサイズが非常に小さく、変更を非常にクエリできる場合、および/または変更を行うだけでなく、何かが起こった場合にロールバックするのに十分なメンテナンスウィンドウがある場合間違っている場合はE、ベースクラステーブルとABサブクラステーブルの間の中間テーブルに正規化することができます(ただし、特定のタイプの直接的な知識はまだありません(AまたはB)基本クラステーブル内)。ただし、これらのテーブルに数億行あり、テーブルを参照する信じられないほどの量のコード(変更が行われたときにテストする必要のあるコード)がある場合、通常は理想主義よりも実用的であることが重要です。そして、これが私が長年取り組まなければならなかった環境です。ベースクラスのテーブルには9億8700万行、615 GBがあり、18台のサーバーに分散しています。また、これらのテーブル(ベースクラスとサブクラスのテーブル)に大量のコードがヒットしたため、開発の量と割り当てられる必要があるQAリソース。

そのため、「最良の」アプローチは状況ごとにしか決定できません。システム(つまり、データの量とテーブルとコードの関連性)、リファクタリングの実行方法、および人員を知る必要があります。あなたが一緒に仕事をすること(あなたのチーム、そしておそらく経営陣-そのようなプロジェクトの賛同を得られますか?)私が言及し、1〜2年間計画していたいくつかの変更があり、それらの85%を実装するために複数のスプリント/リリースを取りました。しかし、行が100万個未満で、これらのテーブルに関連付けられたコードが多くない場合は、おそらくより理想的で「純粋な」側面から始めることができます。

どちらの方法を選択した場合でも、少なくとも次の2年間で(可能であれば)そのモデルがどのように機能するかに注意してください。痛みの原因を正直に評価できるように、その時点で最高のアイデアのように思えたとしても、うまくいったものと痛みを引き起こしたものに注意してください)。とに注意を払う理由を特定の意思決定が働いたかではないので、あなたが決定することができますことをやった可能性が高いが、「より良い」次回:-)ことができます。


17

Martin Fowlerによると、テーブル継承の問題には3つのアプローチがあります。

  • 単一テーブル継承:1つのテーブルがすべてのタイプを表します。未使用の属性はNULLになります。
  • 具体的なテーブルの継承:具体的なタイプごとに1つのテーブル、タイプの各属性の各テーブル列。テーブル間に関係はありません。
  • クラステーブルの継承:タイプごとに1つのテーブル。各テーブルには、継承されていない新しい属性のみの属性があります。テーブルは、実際の型継承階層を反映して関連しています。

これらを出発点として、各アプローチの長所と短所を検索できます。その要点は、すべてのアプローチに大きな欠点があり、どれも圧倒的な利点がないことです。オブジェクトリレーショナルインピーダンスの不整合として知られていますが、この問題はまだ解決策が見つかりません。

個人的には、悪い関係のデザインが引き起こす問題のタイプは、悪いタイプのデザインから生じる問題の種類よりも桁違いに深刻だと思います。悪いデータベース設計は、クエリの遅延、更新の異常、データサイズの爆発、デッドロック、応答のないアプリ、および間違った形式で送信された数十から数百ギガバイトのデータにつながります。型の設計が悪いと、ランタイムではなくコードの保守と更新が困難になります。したがって、私の本では、正しいリレーショナルデザインは、オブジェクト指向型の純度よりも何度も優先されます。


@AlwaysLearningNewStuffこの質問はdba.stackexchange.com/questions/139092のフォローアップだと思いますか?実装で、テーブルの継承があります。
レムスルサヌ

はい、そうです、この質問をする前に、まずタイプ/サブタイプデザインを実装する方法を正しく理解したかどうかを確認したかったのです。いくつかの(すべてではない!)サブクラスが属性を共有している場合、上記の問題に直面します。そのニュアンスを無視するのではなく、その場合のデータモデルを最適化するためにできることがあるのではないかと思っていました
...-AlwaysLearningNewStuff

6

仕様の私の解釈によると、2つの異なる(ただし接続されているスーパータイプサブタイプ構造を実装する方法を見つけたいと考えています。

aformentionedタスクを達成するためのアプローチを露出させるためには、私は問題では、シナリオに2つの古典追加するつもりです架空のエンティティと呼ばれるタイプFooBar、私は意志詳細怒鳴るを。

ビジネスルール

論理モデルの作成に役立ついくつかのステートメントを次に示します。

  • A Foo is either one Bar or one C
  • A Foo is categorized by one FooType
  • A Bar is either one A or one C
  • A Bar is classified by one BarType

論理モデル

そして、結果のIDEF1X [1]論理モデルを図1に示します(また、DropboxからPDFとしてダウンロードすることもできます)。

図1-仮想のスーパータイプとサブタイプの関係データモデル

Foo and Barの追加

私は追加しませんでしたFooし、Barより良いモデルの外観を作るために、それはより表現にします。私はそれらが次の理由で重要であると思います:

  • 通りAB名前の属性を共有しE、この機能は示唆している、彼らは明確な(しかし関連)の一種のサブエンティティタイプであることをコンセプトイベント測定、私はによって表さなど、Bar順番に、である、というスーパーエンティティタイプ階層の最上部に属性Fooを保持するのサブエンティティタイプD

  • 以来C検討中のエンティティタイプの残りの株式のみつの属性、すなわち、Dこのアスペクトほのめかすそれは別の種類のサブエンティティタイプであることをコンセプトイベント測定など、ので、私はのおかげで、このような状況を描写Fooスーパーエンティティタイプ。

ただし、これらは単なる仮定であり、リレーショナルデータベースは特定のビジネスコンテキストのセマンティクスを正確に反映することを意図しているため、特定のドメインで関心のあるすべてのものを識別して分類する必要があります。 。

設計段階の重要な要素

すべての用語を別として、排他的なスーパータイプサブタイプクラスターは通常の関係であるという事実を認識することは非常に便利です。次の方法で状況を説明しましょう。

  • 排他的スーパーエンティティタイプの出現は、1つのサブエンティティタイプの補数のみに関連しています。

したがって、これらの場合には1対1(1:1)の対応(またはカーディナリティー)があります。

これまでの投稿からわかるように、この性質の関連付けを作成するとき、スーパータイプが接続されている正しいサブタイプインスタンスを示すため、discriminator属性(実装時の列)は最重要の役割を果たします。(i)スーパータイプから(ii)サブタイプへの主キーの移行も重要な意味を持ちます。

具体的なDDL構造

次に、上記の論理モデルに基づいたDDL構造を作成しました。

CREATE TABLE FooType -- Look-up table.
(
    FooTypeCode     CHAR(2)  NOT NULL,
    Description     CHAR(90) NOT NULL, 
    CreatedDateTime DATETIME NOT NULL,
    CONSTRAINT PK_FooType             PRIMARY KEY (FooTypeCode),
    CONSTRAINT AK_FooType_Description UNIQUE      (Description)
);

CREATE TABLE Foo -- Supertype
(
    FooId           INT      NOT NULL, -- This PK migrates (1) to ‘Bar’ as ‘BarId’, (2) to ‘A’ as ‘AId’, (3) to ‘B’ as ‘BId’, and (4) to ‘C’ as ‘CId’.
    FooTypeCode     CHAR(2)  NOT NULL, -- Discriminator column.
    D               INT      NOT NULL, -- Column that applies to ‘Bar’ (and therefore to ‘A’ and ‘B’) and ‘C’.
    CreatedDateTime DATETIME NOT NULL,
    CONSTRAINT PK_Foo                 PRIMARY KEY (FooId),
    CONSTRAINT FK_from_Foo_to_FooType FOREIGN KEY (FooTypeCode)
        REFERENCES FooType (FooTypeCode)
);

CREATE TABLE BarType -- Look-up table.
(
    BarTypeCode CHAR(1)  NOT NULL,  
    Description CHAR(90) NOT NULL,  
    CONSTRAINT PK_BarType             PRIMARY KEY (BarTypeCode),
    CONSTRAINT AK_BarType_Description UNIQUE      (Description)
);

CREATE TABLE Bar -- Subtype of ‘Foo’.
(
    BarId       INT     NOT NULL, -- PK and FK.
    BarTypeCode CHAR(1) NOT NULL, -- Discriminator column. 
    E           INT     NOT NULL, -- Column that applies to ‘A’ and ‘B’.
    CONSTRAINT PK_Bar             PRIMARY KEY (BarId),
    CONSTRAINT FK_from_Bar_to_Foo FOREIGN KEY (BarId)
        REFERENCES Foo (FooId),
    CONSTRAINT FK_from_Bar_to_BarType FOREIGN KEY (BarTypeCode)
        REFERENCES BarType (BarTypeCode)    
);

CREATE TABLE A -- Subtype of ‘Bar’.
(
    AId INT NOT NULL, -- PK and FK.
    X   INT NOT NULL, -- Particular column.  
    CONSTRAINT PK_A             PRIMARY KEY (AId),
    CONSTRAINT FK_from_A_to_Bar FOREIGN KEY (AId)
        REFERENCES Bar (BarId)  
);

CREATE TABLE B -- (1) Subtype of ‘Bar’ and (2) supertype of ‘A’ and ‘B’.
(
    BId INT NOT NULL, -- PK and FK.
    Y   INT NOT NULL, -- Particular column.  
    CONSTRAINT PK_B             PRIMARY KEY (BId),
    CONSTRAINT FK_from_B_to_Bar FOREIGN KEY (BId)
        REFERENCES Bar (BarId)  
);

CREATE TABLE C -- Subtype of ‘Foo’.
(
    CId INT NOT NULL, -- PK and FK.
    Z   INT NOT NULL, -- Particular column.  
    CONSTRAINT PK_C             PRIMARY KEY (CId),
    CONSTRAINT FK_from_C_to_Foo FOREIGN KEY (FooId)
        REFERENCES Foo (FooId)  
);

この構造を使用すると、ベーステーブル(またはリレーション)にNULLマークが格納されることを回避できます。これにより、データベースがあいまいになります。

整合性、一貫性、およびその他の考慮事項

データベースを実装したら、(a)各排他的スーパータイプ行が対応するサブタイプの対応物によって常に補完され、(b)そのようなサブタイプ行がスーパータイプ弁別子列に含まれる値と互換性があることを保証する必要があります。したがって、TRANSACTIONSデータベースでこれらの条件が満たされていることを確認するために、ACIDを使用すると非常に便利です。

データベースの論理的な健全性、自己表現性、および正確性を放棄するべきではありません。これらは、データベースをより強固にする側面です。

以前に投稿された2つの回答には、データベースとそのアプリケーションプログラムを設計、作成、管理する際に考慮に値する適切なポイントが既に含まれています。

VIEW定義によるデータの取得

必要なJOIN句を毎回書き込むなどせずに手元のデータを取得できるように、異なるスーパータイプサブタイプグループの列を結合するビューを設定できます。このようにして、目的のVIEW(派生したリレーションまたはテーブル)から簡単に直接SELECTできます。

ご覧のとおり、「テッド」コッドは間違いなく天才でした。彼が遺したツールは非常に強力でエレガントであり、そしてもちろん、互いにうまく統合されています。

関連資料

スーパータイプとサブタイプの関係を含む大規模なデータベースを分析する場合、次のスタックオーバーフローの質問に対して@PerformanceDBAによって提案された並外れた答えを見つけることができます。


注意

1. 情報モデリングの統合定義IDEF1X)は、1993年12月に米国国立標準技術研究所(NIST)によって標準として確立された、非常に推奨されるデータモデリング手法です。(a) EF Codd博士が執筆した初期の理論資料にしっかりと基づいています。上の(b)は、エンティティリレーションシップによって開発されたデータのビュー、ドクター・PP・チェン。また、(c) Robert G. Brownによって作成された論理データベース設計手法についても。IDEF1Xが1次論理によって形式化されたことは注目に値します。


私の雇用主はビジネスロジックを変更し、E完全に削除しました!ユーザーsrutzkyの答えを受け入れる理由は、最も効率的なルートを選択するという決定を下すのに役立つ良い点を提供するからです。そうでない場合、私はあなたの答えを受け入れます。先にあなたの答えを支持しました。再度、感謝します!
AlwaysLearningNewStuff
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.