Внешний ключ должен ссылаться только на одну родительскую таблицу. Это фундаментально как для синтаксиса SQL, так и для теории отношений.
Полиморфная ассоциация - это когда данный столбец может ссылаться на одну из двух или более родительских таблиц. Вы не можете объявить это ограничение в SQL.
Дизайн «Полиморфные ассоциации» нарушает правила проектирования реляционных баз данных. Я не рекомендую использовать его.
Есть несколько альтернатив:
Исключительные дуги: Создание нескольких столбцов внешнего ключа, каждый из которых ссылается на одного родителя. Убедитесь, что только один из этих внешних ключей может быть ненулевым.
Обратное отношение: Используйте три таблицы «многие ко многим», каждая ссылается на Комментарии и соответствующий родительский элемент.
Конкретный суперстолб: Вместо неявного "комментируемого" суперкласса создайте реальную таблицу, на которую ссылается каждая из ваших родительских таблиц. Затем свяжите ваши комментарии с этим суперстабильным. Код псевдо-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.
Что произойдет, например, если вы вставите комментарий и имя "Видео" в качестве имени родительской таблицы для этого Comment
? Нет таблицы с именем "Видео" существует. Должна ли вставка быть прервана с ошибкой? Какое ограничение нарушается? Как СУРБД узнает, что этот столбец должен называть существующую таблицу? Как он обрабатывает имена таблиц без учета регистра?
Аналогично, если вы отбрасываете таблицу Events
, но в Comments
есть строки, которые указывают на события как на своих родителей, каким должен быть результат? Следует ли прекратить удаление таблицы? Должны ли строки в Comments
быть осиротевшими? Должны ли они изменить ссылку на другую существующую таблицу, например Articles
? Имеют ли смысл значения идентификаторов, которые раньше указывали на Events
, когда указывают на Articles
?
Все эти дилеммы связаны с тем фактом, что полиморфные ассоциации зависят от использования данных (то есть строкового значения) для ссылки на метаданные (имя таблицы). Это не поддерживается SQL. Данные и метаданные разделены.
Мне с трудом удается обернуть голову вокруг твоего предложения "Конкретный суперстабль".
Определите 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 неверны в отношении правильного проектирования реляционных баз данных.