外部キーは、1つの親テーブルのみを参照する必要があります。これは、SQL構文とリレーショナル理論の両方の基本です。
ポリモーフィックアソシエーションは、特定の列が2つ以上の親テーブルのいずれかを参照する場合です。SQLでその制約を宣言する方法はありません。
ポリモーフィックアソシエーションの設計は、リレーショナルデータベースの設計のルールに違反しています。使用はお勧めしません。
いくつかの選択肢があります:
排他的アーク: それぞれが1つの親を参照する複数の外部キー列を作成します。これらの外部キーの1つだけがNULL以外になる可能性があることを強制します。
関係を逆にする: 3つの多対多のテーブルを使用し、それぞれがコメントとそれぞれの親を参照します。
具体的なスーパーテーブル: 暗黙の「コメント可能な」スーパークラスの代わりに、各親テーブルが参照する実際のテーブルを作成します。次に、コメントをそのスーパーテーブルにリンクします。疑似Railsコードは次のようになります(私はRailsユーザーではないため、これをリテラルコードではなくガイドラインとして扱います)。
class Commentable < ActiveRecord::Base
has_many :comments
end
class Comment < ActiveRecord::Base
belongs_to :commentable
end
class Article < ActiveRecord::Base
belongs_to :commentable
end
class Photo < ActiveRecord::Base
belongs_to :commentable
end
class Event < ActiveRecord::Base
belongs_to :commentable
end
また、プレゼンテーション「SQLの実用的なオブジェクト指向モデル」と私の著書「SQLアンチパターン:データベースプログラミングの落とし穴の回避」でポリモーフィックな関連付けについても説明します。
コメントについて:はい、外部キーが指していると思われるテーブルの名前を示す別の列があることは知っています。この設計は、SQLの外部キーではサポートされていません。
たとえば、コメントを挿入し、その親テーブルの名前として「Video」という名前を付けた場合はどうなりますComment
か?「ビデオ」という名前のテーブルは存在しません。挿入をエラーで中止する必要がありますか?どのような制約に違反していますか?RDBMSは、この列が既存のテーブルに名前を付けることになっていることをどのように認識しますか?大文字と小文字を区別しないテーブル名はどのように処理されますか?
同様に、Events
テーブルを削除しても、Comments
イベントを親として示す行がある場合、結果はどうなるでしょうか。ドロップテーブルを中止する必要がありますか?行をComments
孤立させる必要がありますか?次のような別の既存のテーブルを参照するように変更する必要がありArticles
ますか?をEvents
指すときに意味をなすために、以前は指していたid値を実行しますArticles
ますか?
これらのジレンマはすべて、ポリモーフィックアソシエーションがメタデータ(テーブル名)を参照するためにデータ(つまり文字列値)を使用することに依存しているという事実によるものです。これはSQLではサポートされていません。データとメタデータは分離されています。
あなたの「ConcreteSupertable」の提案に頭を悩ませるのに苦労しています。
Commentable
Railsモデル定義の形容詞だけでなく、実際のSQLテーブルとして定義します。他の列は必要ありません。
CREATE TABLE Commentable (
id INT AUTO_INCREMENT PRIMARY KEY
) TYPE=InnoDB;
テーブルを定義しArticles
、Photos
、およびEvents
「サブクラス」としてのCommentable
彼らの主キーも外部キー参照可能とすることにより、Commentable
。
CREATE TABLE Articles (
id INT PRIMARY KEY,
FOREIGN KEY (id) REFERENCES Commentable(id)
) TYPE=InnoDB;
Comments
への外部キーを使用してテーブルを定義しますCommentable
。
CREATE TABLE Comments (
id INT PRIMARY KEY AUTO_INCREMENT,
commentable_id INT NOT NULL,
FOREIGN KEY (commentable_id) REFERENCES Commentable(id)
) TYPE=InnoDB;
あなたが作成したい場合にはArticle
(例えば)、あなたは、新しい行を作成する必要がありCommentable
すぎ。Photos
とのためにもEvents
。
INSERT INTO Commentable (id) VALUES (DEFAULT);
INSERT INTO Articles (id, ...) VALUES ( LAST_INSERT_ID(), ... );
INSERT INTO Commentable (id) VALUES (DEFAULT);
INSERT INTO Photos (id, ...) VALUES ( LAST_INSERT_ID(), ... );
INSERT INTO Commentable (id) VALUES (DEFAULT);
INSERT INTO Events (id, ...) VALUES ( LAST_INSERT_ID(), ... );
を作成Comment
する場合は、に存在する値を使用してくださいCommentable
。
INSERT INTO Comments (id, commentable_id, ...)
VALUES (DEFAULT, 2, ...);
特定ののコメントをクエリする場合は、Photo
いくつかの結合を行います。
SELECT * FROM Photos p JOIN Commentable t ON (p.id = t.id)
LEFT OUTER JOIN Comments c ON (t.id = c.commentable_id)
WHERE p.id = 2;
コメントのIDしかなく、コメントの対象となるコメント可能なリソースを見つけたい場合。このため、Commentableテーブルが参照するリソースを指定すると便利な場合があります。
SELECT commentable_id, commentable_type FROM Commentable t
JOIN Comments c ON (t.id = c.commentable_id)
WHERE c.id = 42;
次に、commentable_type
どのテーブルから結合するかを検出した後、2番目のクエリを実行してそれぞれのリソーステーブル(写真、記事など)からデータを取得する必要があります。SQLではテーブルに明示的に名前を付ける必要があるため、同じクエリでこれを行うことはできません。同じクエリのデータ結果によって決定されるテーブルに結合することはできません。
確かに、これらの手順のいくつかは、Railsで使用されている規則に違反しています。しかし、Railsの規則は、適切なリレーショナルデータベースの設計に関して間違っています。
foreign_key
に渡すことができるオプションについて話していませんbelongs_to
。OPは、ネイティブデータベースの「外部キー制約」について話します。それはしばらく私を混乱させました。