外部キーは、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
また、私のプレゼンテーションPractical Object-Oriented Models in SQLと私の著書SQL Antipatterns: Avoiding the Pitfalls of Database Programmingで、ポリモーフィック アソシエーションについて説明しています。
あなたのコメントについて: はい、外部キーが指していると思われるテーブルの名前を示す別の列があることは知っています。この設計は、SQL の外部キーではサポートされていません。
たとえば、コメントを挿入し、その親テーブルの名前として「ビデオ」という名前を付けるとどうなりCommentますか? 「ビデオ」という名前のテーブルは存在しません。挿入をエラーで中止する必要がありますか? どの制約に違反していますか? RDBMS は、この列が既存のテーブルの名前であることをどのように認識しますか? 大文字と小文字を区別しないテーブル名はどのように処理されますか?
同様に、EventsテーブルをドロップしてCommentsも、イベントを親として示す行が含まれている場合、結果はどうなるでしょうか? ドロップ テーブルを中止する必要がありますか? 行をComments孤立させる必要がありますか? などの別の既存のテーブルを参照するように変更する必要がありArticlesますか? Eventsを指すときに意味をなすために使用された id 値はありArticlesますか?
これらのジレンマはすべて、ポリモーフィック アソシエーションがデータ (つまり文字列値) を使用してメタデータ (テーブル名) を参照することに依存しているという事実によるものです。これは SQL ではサポートされていません。データとメタデータは別物です。
あなたの「Concrete Supertable」の提案に頭を悩ませています。
Rails モデル定義Commentableの単なる形容詞ではなく、実際の SQL テーブルとして定義します。他の列は必要ありません。
CREATE TABLE Commentable (
id INT AUTO_INCREMENT PRIMARY KEY
) TYPE=InnoDB;
テーブルArticles、Photos、およびEventsを の「サブクラス」として定義しCommentable、それらの主キーを を参照する外部キーにもしますCommentable。
CREATE TABLE Articles (
id INT PRIMARY KEY, -- not auto-increment
FOREIGN KEY (id) REFERENCES Commentable(id)
) TYPE=InnoDB;
-- similar for Photos and Events.
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); -- generate a new id 1
INSERT INTO Articles (id, ...) VALUES ( LAST_INSERT_ID(), ... );
INSERT INTO Commentable (id) VALUES (DEFAULT); -- generate a new id 2
INSERT INTO Photos (id, ...) VALUES ( LAST_INSERT_ID(), ... );
INSERT INTO Commentable (id) VALUES (DEFAULT); -- generate a new id 3
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 しか持っておらず、そのコメントがコメント可能なリソースを見つけたい場合。このために、コメント可能なテーブルが参照するリソースを指定すると役立つ場合があります。
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 の規則は間違っています。