たとえば、多対多の関係を持つテーブル A とテーブル B があります。交差テーブルである Table C には、A.id と B.id が、2 つの関係を表す値とともに格納されます。または、具体的な例として、ユーザー アカウント、フォーラム、カルマ スコアを持つ stackexchange を想像してみてください。または、学生、コース、および成績。テーブルAとBが非常に大きい場合、テーブルCは非常に急速に大きくなる可能性があり、おそらくそうなるでしょう(実際、そうであると仮定しましょう)。このような問題にどのように対処すればよいでしょうか。これを回避するためにテーブルを設計するより良い方法はありますか?
1 に答える
魔法はありません。接続されている行と接続されていない行がある場合、この情報は何らかの方法で表現する必要があり、それを行う「リレーショナル」な方法は「ジャンクション」(別名「リンク」) テーブルです。はい、ジャンクション テーブルは大きくなる可能性がありますが、幸いなことに、データベースは膨大な量のデータを処理する能力が非常に優れています。
ジャンクション テーブルとコンマ区切りリスト (または類似のもの) を使用することには、次のような正当な理由があります。
- 効率的なクエリ (インデックス作成とクラスタリングによる)。
- 参照整合性の強制。
ジャンクション テーブルを設計するときは、次の質問に答えてください。
- 一方向だけで照会する必要がありますか、それとも両方で照会する必要がありますか? 1
- 一方向の場合は、両方の外部キーに複合 PRIMARY KEY を作成するだけです (それらを PARENT_ID および CHILD_ID と呼びましょう)。順序が重要です。親から子にクエリを実行する場合、PK は {PARENT_ID, CHILD_ID} である必要があります。
- 双方向の場合は、逆の順序で複合インデックスも作成します。この場合は {CHILD_ID, PARENT_ID} です。
- 「余分な」データは小さいですか?
- ジャンクション テーブルが親として機能する追加のテーブルはありますか?
- はいの場合は、子 FK をスリムに保つために代理キーを追加する価値があるかどうかを検討してください。ただし、代理キーを追加すると、おそらくクラスタリングの機会がなくなることに注意してください。
多くの場合、これらの質問に対する答えは、yes と no の両方であり、その場合、テーブルは次のようになります (以下の Oracle 構文)。
CREATE TABLE JUNCTION_TABLE (
PARENT_ID INT,
CHILD_ID INT,
EXTRA_DATA VARCHAR2(50),
PRIMARY KEY (PARENT_ID, CHILD_ID),
FOREIGN KEY (PARENT_ID) REFERENCES PARENT_TABLE (PARENT_ID),
FOREIGN KEY (CHILD_ID) REFERENCES CHILD_TABLE (CHILD_ID)
) ORGANIZATION INDEX COMPRESS;
CREATE UNIQUE INDEX JUNCTION_TABLE_IE1 ON
JUNCTION_TABLE (CHILD_ID, PARENT_ID, EXTRA_DATA) COMPRESS;
考慮事項:
ORGANIZATION INDEX
: ほとんどの DBMS でクラスタリングと呼ばれる Oracle 固有の構文。他の DBMS には独自の構文があり、一部 (MySQL/InnoDB) はクラスタリングを意味し、ユーザーはそれをオフにすることはできません。COMPRESS
注: 一部の DBMS は、最先端のインデックス圧縮をサポートしています。クラスター化されたテーブルは本質的にインデックスであるため、圧縮も適用できます。JUNCTION_TABLE_IE1
,EXTRA_DATA
: 余分なデータはセカンダリ インデックスでカバーされるため、DBMS は子から親の方向にクエリを実行するときに、テーブルに触れずに取得できます。主キーはクラスタリング キーとして機能するため、親から子にクエリを実行するときに余分なデータが自然にカバーされます。
物理的には、2 つの B ツリー (1 つはクラスター化されたテーブルで、もう 1 つはセカンダリ インデックス) しかなく、テーブル ヒープはまったくありません。これにより、クエリのパフォーマンスが向上し (親から子への方向と子から親への方向の両方が単純なインデックス範囲スキャンで満たされます)、行の挿入/削除時のオーバーヘッドがかなり小さくなります。
同等の MS SQL Server 構文 (インデックス圧縮なし) は次のとおりです。
CREATE TABLE JUNCTION_TABLE (
PARENT_ID INT,
CHILD_ID INT,
EXTRA_DATA VARCHAR(50),
PRIMARY KEY (PARENT_ID, CHILD_ID),
FOREIGN KEY (PARENT_ID) REFERENCES PARENT_TABLE (PARENT_ID),
FOREIGN KEY (CHILD_ID) REFERENCES CHILD_TABLE (CHILD_ID)
);
CREATE UNIQUE INDEX JUNCTION_TABLE_IE1 ON
JUNCTION_TABLE (CHILD_ID, PARENT_ID) INCLUDE (EXTRA_DATA);
PRIMARY KEY NONCLUSTEREDが指定されていない限り、MS SQL Server は自動的にテーブルをクラスター化することに注意してください。
1つまり、指定された「親」の「子」のみを取得する必要がありますか、または指定された子の親も取得する必要がある場合があります。
2カバーリングにより、クエリをインデックスだけで満たすことができ、クラスター化されたテーブルのセカンダリ インデックスを介してデータにアクセスするときに必要となる高価なダブル ルックアップを回避できます。
3この方法では、余分なデータが繰り返されることはありません (データが大きいため、コストがかかります) が、ダブル ルックアップを回避し、(安価な) テーブル ヒープ アクセスに置き換えます。ただし、ヒープベースのテーブルでの範囲スキャンのパフォーマンスを損なう可能性のあるクラスタリング要因に注意してください!