Ответ 1
Внешний ключ должен ссылаться только на одну родительскую таблицу. Это имеет фундаментальное значение как для синтаксиса SQL, так и для реляционной теории.
Полиморфная ассоциация - это когда данный столбец может ссылаться на одну или две родительские таблицы. Невозможно объявить это ограничение в SQL.
Конструкция полиморфных ассоциаций нарушает правила проектирования реляционных баз данных. Я не рекомендую использовать его.
Существует несколько альтернатив:
-
Эксклюзивные дуги: Создайте несколько столбцов внешнего ключа, каждый из которых ссылается на одного родителя. Убедитесь, что точно один из этих внешних ключей может быть не NULL.
-
Отменить отношение: Использовать три таблицы "многие-ко-многим", каждая ссылка "Комментарии" и соответствующий родитель.
-
Concrete Supertable: Вместо неявного "комментария" суперкласса создайте реальную таблицу, к которой обращаются каждая из ваших родительских таблиц. Затем свяжите свои комментарии с этим надёжным. Код псевдо-рельсов будет выглядеть примерно следующим образом (я не пользователь 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 Antipatterns: устранение ошибок программирования баз данных.
Повторите свой комментарий: Да, я знаю, что есть еще один столбец, который отмечает имя таблицы, на которое якобы указывает внешний ключ. Этот дизайн не поддерживается внешними ключами в SQL.
Что происходит, например, если вы вставляете комментарий и имя "Видео" в качестве имени родительской таблицы для этого Comment
? Нет таблицы с именем "Видео". Должна ли вставка быть прервана с ошибкой? Какое ограничение нарушается? Как RDBMS знает, что этот столбец должен назвать существующую таблицу? Как он обрабатывает имена таблиц без учета регистра?
Аналогично, если вы отбрасываете таблицу Events
, но у вас есть строки в Comments
, которые указывают на события как на их родителя, каков должен быть результат? Следует ли прервать таблицу отбрасывания? Должны ли строки в Comments
осиротеть? Должны ли они измениться, чтобы ссылаться на другую существующую таблицу, такую как Articles
? Значения id, которые указывали на Events
, имеют смысл при указании на Articles
?
Эти дилеммы связаны с тем, что полиморфные ассоциации зависят от использования данных (т.е. строкового значения) для ссылки на метаданные (имя таблицы). Это не поддерживается SQL. Данные и метаданные являются отдельными.
Мне тяжело обволакивать ваше предложение "Concrete Supertable".
-
Определите
Commentable
как реальную таблицу SQL, а не просто прилагательное в определении модели Rails. Никакие другие столбцы не нужны.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;
-
Если у вас есть только идентификатор комментария, и вы хотите найти, для какого комментируемого ресурса это комментарий. Для этого вы можете обнаружить, что полезно, чтобы таблица Commentable указывала, какой ресурс он ссылается.
SELECT commentable_id, commentable_type FROM Commentable t JOIN Comments c ON (t.id = c.commentable_id) WHERE c.id = 42;
Затем вам нужно будет запустить второй запрос, чтобы получить данные из соответствующей таблицы ресурсов (фотографии, статьи и т.д.) после обнаружения из
commentable_type
, к какой таблице присоединиться. Вы не можете сделать это в том же запросе, потому что SQL требует, чтобы таблицы назывались явно; вы не можете присоединиться к таблице, определенной результатами данных, в том же запросе.
По общему признанию, некоторые из этих шагов нарушают соглашения, используемые Rails. Но соглашения Rails ошибочны в отношении правильной структуры реляционной базы данных.