リンク先のモデルは、部分的なエンティティー属性値(EAV) モデルのように見えます。EAV は非常に柔軟ですが、データの整合性が低く、扱いにくく、通常は非効率的です。リレーショナル モデルの精神とはまったく異なります。いくつかの大規模な電子商取引サイトで働いてきたので、これはこの分野の標準的または優れたデータベース設計手法ではないと言えます。
製品の種類が非常に多くない場合 (最大で数十、ただし数百はありません)、2 つの一般的なアプローチのいずれかを使用してこれを処理できます。
最初のアプローチは、製品の種類ごとに必要となる可能性のあるすべての属性の列を含む、製品用の単一のテーブルを用意することです。製品の種類ごとに適切な列を使用し、残りは空白のままにします。本、音楽、ビデオを販売しているとします。
create table Product (
id integer primary key,
name varchar(255) not null,
type char(1) not null check (type in ('B', 'M', 'V')),
number_of_pages integer, -- book only
duration_in_seconds integer, -- music and video only
classification varchar(2) check (classification in ('U', 'PG', '12', '15', '18')) -- video only
);
これには、単純であり、結合を必要としないという利点があります。ただし、データの整合性を確保するのには適していません (たとえば、ページ数のない本を作成することもできます)。また、製品の種類が複数ある場合は、テーブルが非常に扱いにくくなります。 .
次のように、製品の各タイプに特定の列の値を要求するテーブルレベルのチェック制約を使用して、整合性の問題を解決できます。
check ((case when type = 'B' then (number_of_pages is not null) else true end)))
(Joe Celko へのヒント - SQL で論理的含意を行う方法を調べたところ、非常によく似たチェック制約を構築するためにこの構造でそれを行う例が見つかりました!)
次のように言うかもしれません。
check ((case when type = 'B' then (number_of_pages is not null) else (number_of_pages is null) end)))
タイプに不適切な値が列に含まれる行がないことを確認します。
2 番目のアプローチは、複数のテーブルを使用することです。すべての製品に共通の列を保持する 1 つのベース テーブルと、そのタイプの製品に固有の列を保持する製品のタイプごとに 1 つの補助テーブルです。そう:
create table Product (
id integer primary key,
type char(1) not null check (type in ('B', 'M', 'V')),
name varchar(255) not null
);
create table Book (
id integer primary key references Product,
number_of_pages integer not null
);
create table Music (
id integer primary key references Product,
duration_in_seconds integer not null
);
create table Video (
id integer primary key references Product,
duration_in_seconds integer not null,
classification varchar(2) not null check (classification in ('U', 'PG', '12', '15', '18'))
);
補助テーブルにはメイン テーブルと同じ主キーがあることに注意してください。それらの主キー列は、メイン テーブルへの外部キーでもあります。
このアプローチは依然としてかなり単純であり、整合性を強化するのに適しています。ただし、通常、クエリには結合が含まれます。
select
p.id,
p.name
from
Product p
join Book b on p.id = b.id
where
b.number_of_pages > 300;
メイン テーブルの間違ったタイプの行に対応する補助テーブルに行を作成したり、メイン テーブルの単一の行に対応する複数の補助テーブルに行を作成したりする可能性があるため、整合性はまだ完全ではありません。モデルをさらに改良することで、これを修正できます。主キーをタイプ列を含む複合キーにすると、製品のタイプがその主キーに埋め込まれます (本であれば ('B', 1001) のような主キーになります)。主テーブルを指す外部キーを持つことができるように、タイプ列を補助テーブルに導入する必要があります。その時点で、正しいタイプを必要とする各補助テーブルにチェック制約を追加できます。このような:
create table Product (
type char(1) not null check (type in ('B', 'M', 'V')),
id integer not null,
name varchar(255) not null,
primary key (type, id)
);
create table Book (
type char(1) not null check (type = 'B'),
id integer not null,
number_of_pages integer not null,
primary key (type, id),
foreign key (type, id) references Product
);
これにより、主キーのみを指定して適切なテーブルを簡単にクエリすることもできます。最初にメイン テーブルをクエリしなくても、それが参照している製品の種類をすぐに知ることができます。
上記のスキーマのように、duration 列が 2 つのテーブルで重複している場合のように、列の重複の潜在的な問題がまだあります。共有列に中間補助テーブルを導入することで、これを修正できます。
create table Media (
type char(1) not null check (type in ('M', 'V')),
id integer not null,
duration_in_seconds integer not null,
primary key (type, id),
foreign key (type, id) references Product
);
create table Music (
type char(1) not null check (type = 'M'),
id integer not null,
primary key (type, id),
foreign key (type, id) references Product
);
create table Video (
type char(1) not null check (type = 'V'),
id integer not null,
classification varchar(2) not null check (classification in ('U', 'PG', '12', '15', '18')),
primary key (type, id),
foreign key (type, id) references Product
);
余分な努力をする価値はないと思うかもしれません。ただし、このような状況に対処するために 2 つのアプローチ (単一テーブルと補助テーブル) を組み合わせて、同様の種類の製品用に共有テーブルを用意することを検討してください。
create table Media (
type char(1) not null check (type in ('M', 'V')),
id integer not null,
duration_in_seconds integer not null,
classification varchar(2) check (classification in ('U', 'PG', '12', '15', '18')),
primary key (type, id),
foreign key (type, id) references Product,
check ((case when type = 'V' then (classification is not null) else (classification is null) end)))
);
アプリケーションにまとめられた類似の種類の製品がある場合、これは特に役立ちます。この例では、店頭でオーディオとビデオを一緒に提示するが、書籍は別々に提示する場合、この構造は、メディアの種類ごとに個別の補助テーブルを用意するよりもはるかに効率的な検索をサポートできます。
これらのアプローチはすべて抜け穴を共有しています。補助テーブルに対応する行がなくても、メイン テーブルに行を作成することは依然として可能です。これを修正するには、今度はメイン テーブルから補助テーブルへの 2 番目の外部キー制約セットが必要です。これはいくつかの理由で特に楽しいです: 可能な外部キー関係の 1 つだけを一度に適用したい場合、およびその関係によって 2 つのテーブルの行間に循環依存関係が作成される場合。チェック制約でいくつかの条件を使用して前者を実現し、遅延可能な制約を使用して後者を実現できます。補助テーブルは上記と同じにすることができますが、メイン テーブルは、暫定的に「タイプ フラグ」列と呼ぶものを拡張する必要があります。
create table Product (
type char(1) not null check (type in ('B', 'M', 'V')),
id integer not null,
is_book char(1) null check (is_book is not distinct from (case type when 'B' then type else null end)),
is_music char(1) null check (is_music is not distinct from (case type when 'M' then type else null end)),
is_video char(1) null check (is_video is not distinct from (case type when 'V' then type else null end)),
name varchar(255) not null,
primary key (type, id)
);
型フラグ列は基本的に列の繰り返しであり、type
潜在的な型ごとに 1 つずつあり、製品がその型である場合にのみ設定されます (これらのチェック制約によって強制されます)。これらは実際の列であるため、値が完全に予測可能であっても、行を挿入するときに値を指定する必要があります。これは少し醜いですが、ショーストッパーではないことを願っています.
これらを配置すると、すべてのテーブルが作成された後、型の代わりに型フラグを使用して、特定の補助テーブルを指す外部キーを形成できます。
alter table Product add foreign key (is_book, id) references Book deferrable initially deferred;
alter table Product add foreign key (is_music, id) references Music deferrable initially deferred;
alter table Product add foreign key (is_video, id) references Video deferrable initially deferred;
重要なことは、外部キー リレーションシップを強制するには、そのすべての構成列が非 null である必要があることです。したがって、任意の行について、null 以外の型フラグは 1 つだけであるため、1 つの関係のみが適用されます。これらの制約は遅延可能であるため、補助テーブルに必要な行が存在する前に、メイン テーブルに行を挿入することができます。トランザクションがコミットされる前に挿入されている限り、それはすべてボードの上にあります。