外部キーは、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 の規則は間違っています。