Зовнішній ключ повинен посилатися лише на одну батьківську таблицю. Це є фундаментальним як для синтаксису 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
?
Усі ці дилеми пов’язані з тим, що Polymorphic Associations залежить від використання даних (тобто значення рядка) для посилання на метадані (назва таблиці). Це не підтримується 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,
FOREIGN KEY (id) REFERENCES Commentable(id)
) TYPE=InnoDB;
Визначте 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);
INSERT INTO Articles (id, ...) VALUES ( LAST_INSERT_ID(), ... );
INSERT INTO Commentable (id) VALUES (DEFAULT);
INSERT INTO Photos (id, ...) VALUES ( LAST_INSERT_ID(), ... );
INSERT INTO Commentable (id) VALUES (DEFAULT);
INSERT INTO Events (id, ...) VALUES ( LAST_INSERT_ID(), ... );
Коли ви хочете створити a 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 є неправильними щодо належного проектування реляційних баз даних.
foreign_key
варіант, на який можна перейтиbelongs_to
. ОП говорить про "обмеження зовнішнього ключа" власної бази даних. Це мене на деякий час збентежило.