web-dev-qa-db-ja.com

2つの可能なテーブルの1つにMySQL外部キーを実行できますか?

さて、ここに私の問題があります。3つのテーブルがあります。地域、国、州。国は地域内にあり、州は地域内にあります。地域は食物連鎖の頂点です。

次に、2つの列を持つPopular_areasテーブルを追加します。 region_idおよびpopular_place_id。 Popular_place_idをいずれかの国の外部キーにすることは可能ですか[〜#〜] or [〜#〜]状態。 idがいずれかの方法で国または州を記述しているかどうかを判断するには、popular_place_type列を追加する必要があります。

165

あなたが説明しているものは、多態的関連と呼ばれます。つまり、「外部キー」列には、ターゲット表のセットのいずれかに存在する必要がある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つの追加テーブルを作成します。たとえば、statesおよびcountriesをそれぞれ参照する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の主キーにすることもできます)。

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つの意味を持つ列であるためです。それは州または国です。人のagephone_numberを単一の列に保存することはありません。同じ理由で、state_idcountry_idの両方を単一の列に保存するべきではありません。これら2つの属性に互換性のあるデータ型があるという事実は偶然です。それらはまだ異なる論理エンティティを意味します。

ポリモーフィックな関連付けも Third Normal Form に違反します。これは、列の意味が、外部キーが参照するテーブルに名前を付ける余分な列に依存するためです。第3正規形では、テーブルの属性はそのテーブルの主キーのみに依存する必要があります。


@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の個々の行にある値に基づいて、結合されたテーブルを動的に選択することはできません。

他の唯一のオプションは、外部結合を使用してallフィルターテーブルに結合することです。一致するproduct_idがないものは、1行のnullとして返されます。ただし、結合テーブルをallハードコードする必要があり、新しいフィルターテーブルを追加する場合は、コードを更新する必要があります。

SELECT * FROM Products
LEFT OUTER JOIN FiltersType1 USING (product_id)
LEFT OUTER JOIN FiltersType2 USING (product_id)
LEFT OUTER JOIN FiltersType3 USING (product_id)
...

すべてのフィルターテーブルに結合する別の方法は、連続して行うことです。

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)
...

ただし、この形式では、すべてのテーブルへの参照を記述する必要があります。それを回避する方法はありません。

251
Bill Karwin

これは世界で最もエレガントなソリューションではありませんが、 concrete table inheritance を使用してこれを機能させることができます。

概念的には、3つのタイプの場所が継承する「人気のある領域になり得るもの」のクラスの概念を提案しています。これは、たとえば、placesと呼ばれるテーブルとして表すことができます。この場合、各行は、regionscountries、またはstatesの行と1対1の関係を持ちます。 。 (地域、国、または州間で共有されている属性(ある場合)は、この場所テーブルにプッシュできます。)あなたのpopular_place_idは、placesテーブルの行への外部キー参照になり、地域、国、または州に移動します。

関連付けのタイプを説明する2番目の列で提案する解決策は、たまたまRailsがポリモーフィックな関連付けを処理する方法ですが、私は一般的にはそのファンではありません。ポリモーフィックな関連付けはあなたの友達ではありません。

10
Abie

以下は、複合キー( 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はまだ市場に出ていません。

5
onedaywhen

私はこのスレッドが古いことを認識していますが、私はこれを見て、解決策が思い浮かび、それをそこに捨てると思いました。

地域、国、および州は、階層にある地理的な場所です。

3つの行(Region、Country、State)を入力するgeological_location_typeというドメインテーブルを作成することにより、問題を完全に回避できます。

次に、3つのロケーションテーブルの代わりに、geographical_location_type_idの外部キーを持つ単一のgeometric_locationテーブルを作成します(インスタンスがRegion、Country、Stateのいずれであるかがわかります)。

Stateインスタンスがその親CountryインスタンスにfKeyを保持し、さらにその親CountryインスタンスがfKeyをその親Regionインスタンスに保持するように、このテーブルを自己参照させることにより、階層をモデル化します。領域インスタンスは、そのfKeyにNULLを保持します。これは、3つのテーブルで行ったこと(1-地域と国の間、および国と州の間の多くの関係)と同じです。ただし、現在はすべて1つのテーブルになっています。

Popular_user_locationテーブルは、ユーザーとgeorgraphical_locationの間のスコープ解決テーブルになります(多くのユーザーが多くの場所を好きになる可能性があります)。

スー…

enter image description here

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です。

0
Toolsmythe