2つの可能なテーブルの1つにMySQL外部キーを実行できますか?
-
22-07-2019 - |
質問
さて、ここに私の問題があります。3つのテーブルがあります。地域、国、州。国は地域内にあり、州は地域内にあります。地域は食物連鎖のトップです。
今、2つの列を持つPopular_areasテーブルを追加しています。 region_idおよびpopular_place_id。 Popular_place_idを OR のいずれかの国の外部キーにすることは可能ですか? IDが国を表すのか州を表すのかを判断するために、popular_place_type列を追加する必要があるでしょう。
解決
あなたが説明しているものは、ポリモーフィック関連と呼ばれます。つまり、「外部キー」は列には、ターゲットテーブルのセットの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制約を使用してポリモーフィックな関連付けをモデル化する方法はありません。外部キー制約は、常に one ターゲットテーブルを参照します。
多態的な関連付けは、RailsやHibernateなどのフレームワークでサポートされています。ただし、この機能を使用するにはSQL制約を無効にする必要があると明示的に述べています。代わりに、アプリケーションまたはフレームワークは同等の作業を実行して、参照が満たされていることを確認する必要があります。つまり、外部キーの値は、可能なターゲットテーブルの1つに存在します。
ポリモーフィックな関連付けは、データベースの一貫性を強化するという点で弱いです。データの整合性は、同じ参照整合性ロジックが適用されたデータベースにアクセスするすべてのクライアントに依存します。また、適用にはバグがない必要があります。
データベースによって実行される参照整合性を利用する代替ソリューションを次に示します。
ターゲットごとに1つの追加テーブルを作成します。たとえば、 state
および popular_states
および popular_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 の主キーにすることもできます) code>)。
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
と1つの列に country_id
。これら2つの属性に互換性のあるデータ型があるという事実は偶然です。それらはまだ異なる論理エンティティを意味します。
Polymorphic Associationsは、第3正規形にも違反しています。列の意味は、外部キーが参照するテーブルに名前を付ける列。第3正規形では、テーブルの属性はそのテーブルの主キーのみに依存する必要があります。
@SavasVedovaからのコメント:
表の定義やクエリの例を見ずに説明に従うかどうかはわかりませんが、複数の Filters
表があり、それぞれに中央の<を参照する外部キーが含まれているようですcode> 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...
製品を特定のタイプのフィルターに結合するのは、どのty
他のヒント
これは世界で最もエレガントなソリューションではありませんが、具体的なテーブル継承これを機能させる。
概念的には、「人気エリアになり得るもの」のクラスの概念を提案しています。 3種類の場所が継承されます。これは、たとえば、各行が regions
、 countries の行と1対1の関係を持つ
places
と呼ばれるテーブルとして表すことができます。 code>、または states
。 (地域、国、または州間で共有される属性は、このプレーステーブルにプッシュできます。) popular_place_id
は、プレーステーブルの行への外部キー参照になります。その後、地域、国、または州に移動します。
アソシエーションのタイプを説明する2番目の列で提案するソリューションは、Railsがポリモーフィックアソシエーションを処理する方法です。ビルは、ポリモーフィックな関連性があなたの友人ではない理由を非常に詳しく説明しています。
これは、ビル・カーウィンの「スーパーテーブル」の修正です。複合キー(place_type、place_id)
を使用して、知覚される通常のフォーム違反を解決するアプローチ:
CREATE TABLE places (
place_id INT NOT NULL UNIQUE,
place_type VARCHAR(10) NOT NULL
CHECK ( place_type = 'state', 'country' ),
UNIQUE ( place_type, place_id )
);
CREATE TABLE states (
place_id INT NOT NULL UNIQUE,
place_type VARCHAR(10) DEFAULT 'state' NOT NULL
CHECK ( place_type = 'state' ),
FOREIGN KEY ( place_type, place_id )
REFERENCES places ( place_type, place_id )
-- attributes specific to states go here
);
CREATE TABLE countries (
place_id INT NOT NULL UNIQUE,
place_type VARCHAR(10) DEFAULT 'country' NOT NULL
CHECK ( place_type = 'country' ),
FOREIGN KEY ( place_type, place_id )
REFERENCES places ( place_type, place_id )
-- attributes specific to country go here
);
CREATE TABLE popular_areas (
user_id INT NOT NULL,
place_id INT NOT NULL,
UNIQUE ( user_id, place_id ),
FOREIGN KEY ( place_type, place_id )
REFERENCES places ( place_type, place_id )
);
この設計では、 places
のすべての行に states
または countries
(両方ではない)の行が存在することを保証できません。これは、SQLの外部キーの制限です。 SQL-92標準に完全に準拠したDBMSでは、遅延テーブル間制約を定義できます。これにより、同じことを実現できますが、不格好で、トランザクションを伴い、そのようなDBMSはまだ市場に出ていません。
このスレッドは古いことを認識していますが、これを見て、解決策が思い浮かんだので、そこに捨てると思いました。
地域、国、および州は、階層に存在する地理的な場所です。
3つの行(Region、Country、State)を入力するgeometric_location_typeというドメインテーブルを作成することで、問題を完全に回避できます。
次に、3つのロケーションテーブルの代わりに、geographical_location_type_idの外部キーを持つ1つのgeometric_locationテーブルを作成します(インスタンスがRegion、Country、Stateのいずれであるかがわかります)。
このテーブルを自己参照することで階層をモデル化し、Stateインスタンスがその親CountryインスタンスにfKeyを保持し、それがさらに親RegionインスタンスにfKeyを保持するようにします。領域インスタンスは、そのfKeyにNULLを保持します。これは、3つのテーブルで行ったことと同じです(1-地域と国の間、および国と州の間の多くの関係があります)。ただし、すべてが1つのテーブルになっています。
popular_user_locationテーブルは、ユーザーとgeorgraphical_locationの間のスコープ解決テーブルになります(多くのユーザーが多くの場所を好きになる可能性があります)。
Soooo…
CREATE TABLE [geographical_location_type] (
[geographical_location_type_id] INTEGER NOT NULL,
[name] VARCHAR(25) NOT NULL,
CONSTRAINT [PK_geographical_location_type] PRIMARY KEY ([geographical_location_type_id])
)
-- Add 'Region', 'Country' and 'State' instances to the above table
CREATE TABLE [geographical_location] (
[geographical_location_id] BIGINT IDENTITY(0,1) NOT NULL,
[name] VARCHAR(1024) NOT NULL,
[geographical_location_type_id] INTEGER NOT NULL,
[geographical_location_parent] BIGINT, -- self referencing; can be null for top-level instances
CONSTRAINT [PK_geographical_location] PRIMARY KEY ([geographical_location_id])
)
CREATE TABLE [user] (
[user_id] BIGINT NOT NULL,
[login_id] VARCHAR(30) NOT NULL,
[password] VARCHAR(512) NOT NULL,
CONSTRAINT [PK_user] PRIMARY KEY ([user_id])
)
CREATE TABLE [popular_user_location] (
[popular_user_location_id] BIGINT NOT NULL,
[user_id] BIGINT NOT NULL,
[geographical_location_id] BIGINT NOT NULL,
CONSTRAINT [PK_popular_user_location] PRIMARY KEY ([popular_user_location_id])
)
ALTER TABLE [geographical_location] ADD CONSTRAINT [geographical_location_type_geographical_location]
FOREIGN KEY ([geographical_location_type_id]) REFERENCES [geographical_location_type] ([geographical_location_type_id])
ALTER TABLE [geographical_location] ADD CONSTRAINT [geographical_location_geographical_location]
FOREIGN KEY ([geographical_location_parent]) REFERENCES [geographical_location] ([geographical_location_id])
ALTER TABLE [popular_user_location] ADD CONSTRAINT [user_popular_user_location]
FOREIGN KEY ([user_id]) REFERENCES [user] ([user_id])
ALTER TABLE [popular_user_location] ADD CONSTRAINT [geographical_location_popular_user_location]
FOREIGN KEY ([geographical_location_id]) REFERENCES [geographical_location] ([geographical_location_id])
ターゲットDBが何であるかわかりませんでした。上記はMS SQL Serverです。