あなたが説明しているのは多形関連と呼ばれています。つまり、「外部キー」列には、ターゲットテーブルのセットの1つに存在する必要があるid値が含まれています。通常、ターゲットテーブルは、データの一般的なスーパークラスのインスタンスであるなど、何らかの方法で関連付けられます。また、外部キー列の横に別の列が必要です。これにより、各行で、参照するターゲットテーブルを指定できます。
CREATE TABLE popular_places (
user_id INT NOT NULL,
place_id INT NOT NULL,
place_type VARCHAR(10) -- either 'states' or 'countries'
-- foreign key is not possible
);
SQL制約を使用してポリモーフィックアソシエーションをモデル化する方法はありません。外部キー制約は常に1つを参照します ターゲットテーブル。
多形アソシエーションは、RailsやHibernateなどのフレームワークによってサポートされています。ただし、この機能を使用するにはSQL制約を無効にする必要があると明示的に言われています。代わりに、アプリケーションまたはフレームワークは、参照が満たされることを保証するために同等の作業を行う必要があります。つまり、外部キーの値は、可能なターゲットテーブルの1つに存在します。
多態的な関連付けは、データベースの整合性を強制することに関して弱いです。データの整合性は、同じ参照整合性ロジックが適用された状態でデータベースにアクセスするすべてのクライアントに依存します。また、適用にはバグがない必要があります。
データベースで強制された参照整合性を利用するいくつかの代替ソリューションは次のとおりです。
ターゲットごとに1つの追加テーブルを作成します。 例:popular_states
およびpopular_countries
、states
を参照します およびcountries
それぞれ。これらの「人気のある」テーブルはそれぞれ、ユーザーのプロファイルも参照しています。
CREATE TABLE popular_states (
state_id INT NOT NULL,
user_id INT NOT NULL,
PRIMARY KEY(state_id, user_id),
FOREIGN KEY (state_id) REFERENCES states(state_id),
FOREIGN KEY (user_id) REFERENCES users(user_id),
);
CREATE TABLE popular_countries (
country_id INT NOT NULL,
user_id INT NOT NULL,
PRIMARY KEY(country_id, user_id),
FOREIGN KEY (country_id) REFERENCES countries(country_id),
FOREIGN KEY (user_id) REFERENCES users(user_id),
);
これは、ユーザーの人気のあるお気に入りの場所をすべて取得するには、これらのテーブルの両方にクエリを実行する必要があることを意味します。しかし、それはあなたが一貫性を強制するためにデータベースに頼ることができることを意味します。
places
を作成する スーパーテーブルとしてのテーブル。 Abieが言及しているように、2番目の選択肢は、人気のある場所がplaces
のようなテーブルを参照することです。 、これは両方のstates
の親です およびcountries
。つまり、州と国の両方に、places
への外部キーもあります。 (この外部キーをstates
の主キーにすることもできます およびcountries
。
CREATE TABLE popular_areas (
user_id INT NOT NULL,
place_id INT NOT NULL,
PRIMARY KEY (user_id, place_id),
FOREIGN KEY (place_id) REFERENCES places(place_id)
);
CREATE TABLE states (
state_id INT NOT NULL PRIMARY KEY,
FOREIGN KEY (state_id) REFERENCES places(place_id)
);
CREATE TABLE countries (
country_id INT NOT NULL PRIMARY KEY,
FOREIGN KEY (country_id) REFERENCES places(place_id)
);
2つの列を使用します。 2つのターゲットテーブルのいずれかを参照する可能性のある1つの列の代わりに、2つの列を使用します。これらの2つの列はNULL
である可能性があります;実際、そのうちの1つだけがNULL
以外である必要があります 。
CREATE TABLE popular_areas (
place_id SERIAL PRIMARY KEY,
user_id INT NOT NULL,
state_id INT,
country_id INT,
CONSTRAINT UNIQUE (user_id, state_id, country_id), -- UNIQUE permits NULLs
CONSTRAINT CHECK (state_id IS NOT NULL OR country_id IS NOT NULL),
FOREIGN KEY (state_id) REFERENCES places(place_id),
FOREIGN KEY (country_id) REFERENCES places(place_id)
);
関係理論の観点から、多形関連は第一正規形
に違反しています。 、popular_place_id
事実上、2つの意味を持つ列です。それは州または国のいずれかです。人のage
は保存しません とそのphone_number
1つの列に、同じ理由で両方のstate_id
を保存しないでください およびcountry_id
単一の列に。これらの2つの属性に互換性のあるデータ型があるという事実は偶然です。それらは依然として異なる論理エンティティを意味します。
多形関連も
@SavasVedovaからのコメントを再確認してください:
テーブルの定義やクエリの例を見ずに説明に従うかどうかはわかりませんが、複数のFilters
があるようです。 それぞれが中央のProducts
を参照する外部キーを含むテーブル テーブル。
CREATE TABLE Products (
product_id INT PRIMARY KEY
);
CREATE TABLE FiltersType1 (
filter_id INT PRIMARY KEY,
product_id INT NOT NULL,
FOREIGN KEY (product_id) REFERENCES Products(product_id)
);
CREATE TABLE FiltersType2 (
filter_id INT PRIMARY KEY,
product_id INT NOT NULL,
FOREIGN KEY (product_id) REFERENCES Products(product_id)
);
...and other filter tables...
参加するタイプがわかっている場合は、製品を特定のタイプのフィルターに簡単に参加できます。
SELECT * FROM Products
INNER JOIN FiltersType2 USING (product_id)
フィルタタイプを動的にする場合は、SQLクエリを作成するためのアプリケーションコードを作成する必要があります。 SQLでは、クエリを作成するときにテーブルを指定して修正する必要があります。 Products
の個々の行にある値に基づいて結合テーブルを動的に選択することはできません。 。
他の唯一のオプションは、すべてに参加することです。 外部結合を使用してテーブルをフィルタリングします。一致するproduct_idがないものは、nullの単一行として返されます。ただし、それでもすべてをハードコーディングする必要があります 結合されたテーブル。新しいフィルターテーブルを追加する場合は、コードを更新する必要があります。
SELECT * FROM Products
LEFT OUTER JOIN FiltersType1 USING (product_id)
LEFT OUTER JOIN FiltersType2 USING (product_id)
LEFT OUTER JOIN FiltersType3 USING (product_id)
...
すべてのフィルターテーブルに結合するもう1つの方法は、シリアルに結合することです。
SELECT * FROM Product
INNER JOIN FiltersType1 USING (product_id)
UNION ALL
SELECT * FROM Products
INNER JOIN FiltersType2 USING (product_id)
UNION ALL
SELECT * FROM Products
INNER JOIN FiltersType3 USING (product_id)
...
ただし、この形式でも、すべてのテーブルへの参照を書き込む必要があります。それを回避することはできません。