Почему у вас не может быть внешнего ключа в полиморфной ассоциации, такой как та, которая представлена ниже как модель Rails?
class Comment < ActiveRecord::Base
belongs_to :commentable, :polymorphic => true
end
class Article < ActiveRecord::Base
has_many :comments, :as => :commentable
end
class Photo < ActiveRecord::Base
has_many :comments, :as => :commentable
#...
end
class Event < ActiveRecord::Base
has_many :comments, :as => :commentable
end
foreign_key
параметре, который может быть переданbelongs_to
. OP говорит об «ограничении внешнего ключа» собственной базы данных. Это меня на время смутило.Ответы:
Внешний ключ должен ссылаться только на одну родительскую таблицу. Это фундаментально как для синтаксиса SQL, так и для теории отношений.
Полиморфная ассоциация - это когда данный столбец может ссылаться на одну из двух или более родительских таблиц. Вы не можете объявить это ограничение в SQL.
Дизайн полиморфных ассоциаций нарушает правила проектирования реляционных баз данных. Не рекомендую его использовать.
Есть несколько альтернатив:
Эксклюзивные дуги: создайте несколько столбцов внешнего ключа, каждый из которых ссылается на одного родителя. Обеспечьте, чтобы ровно один из этих внешних ключей не мог быть NULL.
Обратное отношение: используйте три таблицы типа «многие ко многим», каждая из которых ссылается на комментарии и соответствующий родительский элемент.
Конкретная супертаблица: вместо неявного суперкласса с комментариями создайте реальную таблицу, на которую ссылается каждая из ваших родительских таблиц. Затем свяжите свои комментарии с этой надтаблицей. Код псевдорельсов будет примерно таким (я не являюсь пользователем 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
? Имеют ли значения id, которые раньше указывали,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;
Когда у вас есть только идентификатор комментария, и вы хотите узнать, для какого ресурса, к которому есть комментарии, это комментарий. Для этого вы можете обнаружить, что для таблицы комментариев полезно указать, на какой ресурс она ссылается.
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 неверны в отношении правильного проектирования реляционной базы данных.
источник
Билл Карвин прав в том, что внешние ключи не могут использоваться с полиморфными отношениями из-за того, что SQL на самом деле не имеет полиморфных отношений собственной концепции. Но если ваша цель иметь внешний ключ - обеспечить ссылочную целостность, вы можете смоделировать его с помощью триггеров. Это становится специфичным для БД, но ниже приведены некоторые недавние триггеры, которые я создал для имитации поведения каскадного удаления внешнего ключа в полиморфных отношениях:
CREATE FUNCTION delete_related_brokerage_subscribers() RETURNS trigger AS $$ BEGIN DELETE FROM subscribers WHERE referrer_type = 'Brokerage' AND referrer_id = OLD.id; RETURN NULL; END; $$ LANGUAGE plpgsql; CREATE TRIGGER cascade_brokerage_subscriber_delete AFTER DELETE ON brokerages FOR EACH ROW EXECUTE PROCEDURE delete_related_brokerage_subscribers(); CREATE FUNCTION delete_related_agent_subscribers() RETURNS trigger AS $$ BEGIN DELETE FROM subscribers WHERE referrer_type = 'Agent' AND referrer_id = OLD.id; RETURN NULL; END; $$ LANGUAGE plpgsql; CREATE TRIGGER cascade_agent_subscriber_delete AFTER DELETE ON agents FOR EACH ROW EXECUTE PROCEDURE delete_related_agent_subscribers();
В моем коде запись в
brokerages
таблице или запись вagents
таблице может относиться к записи вsubscribers
таблице.источник