web-dev-qa-db-ja.com

データベース設計:「(多対多)対多」関係の正規化

短縮版

既存の多対多結合の各ペアに固定数の追加プロパティを追加する必要があります。以下の図にスキップして、ベースケースを拡張してこれを達成するための利点と欠点の観点から、オプション1〜4のどれが最適ですか。または、ここで検討しなかったより良い代替案はありますか?

長いバージョン

現在、中間結合テーブルを介して、多対多の関係にある2つのテーブルがあります。次に、既存のオブジェクトのペアに属するプロパティへのリンクを追加する必要があります。プロパティテーブルの1つのエントリが複数のペアに適用される場合があります(または、1つのペアに対して複数回使用される場合もあります)。私はこれを行うための最良の方法を決定しようとしています、そして状況の考え方を整理するのに苦労しています。意味的には、次のいずれかと同じようにうまく説明できるようです。

  1. 固定数の追加プロパティの1つのセットにリンクされた1つのペア
  2. 多くの追加プロパティにリンクされた1つのペア
  3. 1つのプロパティセットにリンクされた多くの(2つの)オブジェクト
  4. 多くのプロパティにリンクされた多くのオブジェクト

XとYの2つのオブジェクトタイプがあり、それぞれ一意のIDとリンクテーブルobjx_objyx_idおよびy_id。リンクの主キーを形成します。各Xは多くのYに関連付けることができ、その逆も可能です。これは、既存の多対多の関係の設定です。

規範事例

Base case

さらに、別のテーブルで定義された一連のプロパティと、特定の(X、Y)ペアがプロパティPを持つ必要がある一連の条件があります。条件の数は固定されており、すべてのペアで同じです。結合の各ペアの3つのシチュエーション/条件について、基本的に「シチュエーションC1では、ペア(X1、Y1)にプロパティP1があります」、「シチュエーションC2、ペア(X1、Y1)にはプロパティP2があります」などです。テーブル。

オプション1

私の現在の状況では、そのような条件がちょうど3つあり、増加することを期待する理由はないので、1つの可能性は列を追加することですc1_p_idc2_p_idc3_p_idからfeatx_featy、指定されたx_idおよびy_id、どのプロパティp_id 3つのケースのそれぞれで使用します。

Option 1

機能に適用されるすべてのプロパティを選択するためにSQLを複雑にし、より多くの条件に容易にスケーリングできないため、これは私には素晴らしいアイデアのようには思えません。ただし、(X、Y)ペアごとに一定数の条件の要件を強制します。実際、それがここで行う唯一のオプションです。

オプション2

条件テーブルcondを作成し、結合テーブルの主キーに条件IDを追加します。

Option 2

これの1つの欠点は、各ペアの条件の数を指定しないことです。もう1つは、以下のようなものとの初期の関係のみを検討している場合です。

SELECT objx.*, objy.* FROM objx
  INNER JOIN objx_objy ON objx_objy.x_id = objx.id
  INNER JOIN objy ON objy.id = objx_objy.y_id

次に、DISTINCT句を追加して、エントリの重複を回避する必要があります。これは、各ペアが一度だけ存在する必要があるという事実を失ったようです。

オプション3

結合テーブルに新しい「ペアID」を作成し、最初のリンクテーブルとプロパティおよび条件の間に2番目のリンクテーブルを作成します。

Option 3

これには、各ペアに対して一定数の条件を適用しないことを除いて、欠点が最も少ないようです。しかし、既存のIDだけを識別する新しいIDを作成することには意味がありますか?

オプション4(3b)

基本的にオプション3と同じですが、追加のIDフィールドは作成されません。これは、両方の元のIDを新しい結合テーブルに配置することで実現され、x_idおよびy_idフィールドの代わりにxy_id

Option 4

このフォームのもう1つの利点は、既存のテーブルが変更されないことです(ただし、まだ本番にはありません)。ただし、基本的にはテーブル全体を複数回複製する(またはとにかくそのように感じる)ため、理想的とは言えません。

概要

オプション3と4は、どちらか一方を使用できるほど十分に似ていると感じています。プロパティへのリンクの数が少なく、固定されているという要件がなければ、おそらく私は今までに持っていたでしょう。それにより、オプション1は他の場合よりも合理的に思えます。非常に限られたテストに基づいて、クエリにDISTINCT句を追加しても、この状況ではパフォーマンスに影響を与えないようですが、オプション2が他の状況と同様に状況を表しているかどうかはわかりません。リンクテーブルの複数の行に同じ(X、Y)ペアを配置することによって引き起こされる固有の重複。

これらのオプションの1つが最善の方法ですか、それとも検討すべき別の構造がありますか?

14
  • オプション1

    *これはSQLを複雑にし、機能に適用されるすべてのプロパティを選択するので、私には良い考えではないようです…

    必ずしもクエリSQLが複雑になるわけではありません(以下の結論を参照)。

    …そしてより多くの条件に容易に対応できません…

    一定数の条件があり、数十または数百でない限り、より多くの条件に容易にスケーリングします。

    ただし、(X、Y)ペアごとに一定数の条件の要件を強制します。実際、これが唯一のオプションです*。

    それはあり、あなたはコメントでこれが「私の要件の中で最も重要ではない」と言っていますが、それはまったく問題ではないと述べていません。

  • オプション2

    これの1つの欠点は、各ペアの条件の数を指定しないことです。もう1つは、最初の関係のみを検討している場合です。次に、重複エントリを回避するためにDISTINCT句を追加する必要があります...

    あなたが言及する複雑さのために、あなたはこのオプションを却下できると思います。 objx_objyテーブルは、一部のクエリの駆動テーブルになる可能性があります(たとえば、「機能に適用されるすべてのプロパティを選択する」、つまり、objxまたはobjyに適用されるすべてのプロパティを意味します)。ビューを使用してDISTINCTを事前に適用できるので、クエリを複雑にすることは問題ではありませんが、ほとんどパフォーマンスが向上しないため、パフォーマンスが大幅に低下します。

  • オプション3

    しかし、既存のIDだけを識別する新しいIDを作成することには意味がありますか?

    いいえ、ありません—あらゆる点でオプション4の方が優れています。

  • オプション4

    …基本的にテーブル全体を複数回複製する(または、とにかくそのように感じる)ので、理想的ではないようです。

    このオプションは問題ありません。プロパティの数が可変であるか、変更される可能性がある場合、これは関係を設定する明白な方法です

結論

objx_objyあたりのプロパティの数が安定している可能性が高く、さらにわずかな数を追加することを想像できない場合、私の好みはオプション1です。これは、「プロパティ数= 3」の制約を適用する唯一のオプションでもあります。オプション4に同様の制約を適用するには、とにかくc1_p_id…列をxyテーブルに追加する必要があります*。

その条件について本当にあまり気にせず、プロパティの数の条件が安定することを疑う理由がある場合は、オプション4を選択します。

どちらかわからない場合は、オプション1を選択してください。他の人が言ったように、オプション1を選択する方が簡単です。オプション1を先延ばしにした場合、「機能に適用されるすべてのプロパティを選択するSQLが複雑になるため…」オプション4の追加のテーブルと同じデータを提供するビューを作成することをお勧めします。

オプション1テーブル:

create table prop(id integer primary key);
create table objx(id integer primary key);
create table objy(id integer primary key);

create table objx_objy(
  x_id integer references objx
, y_id integer references objy
, c1_p_id integer not null references prop
, c2_p_id integer not null references prop
, c3_p_id integer not null references prop
, primary key (x_id, y_id)
);

insert into prop(id) select generate_series(90,99);
insert into objx(id) select generate_series(10,12);
insert into objy(id) select generate_series(20,22);

insert into objx_objy(x_id,y_id,c1_p_id,c2_p_id,c3_p_id)
select objx.id, objy.id, 90, 91, 90+floor(random()*10)
from objx cross join objy;

「エミュレート」オプション4を表示:

create view objx_objy_prop as
select x_id
     , y_id
     , unnest(array[1,2,3]) c_id
     , unnest(array[c1_p_id,c2_p_id,c3_p_id]) p_id
from objx_objy;

"フィーチャーに適用されているすべてのプロパティを選択してください":

select distinct p_id from objx_objy_prop where x_id=10 order by p_id;

/*
|p_id|
|---:|
|  90|
|  91|
|  97|
|  98|
*/

dbfiddle ここ