Почему у Вас не может быть внешнего ключа в полиморфной ассоциации?

Внешние библиотеки Javascript и виджеты часто используют исключения для ошибок создания экземпляров. Обычно нужно:

try {
  var w = new Widget();
}
catch (e) {
  // widget failed
}
75
задан eggdrop 28 May 2009 в 17:32
поделиться

1 ответ

Внешний ключ должен ссылаться только на одну родительскую таблицу. Это фундаментально как для синтаксиса SQL, так и для теории отношений.

Полиморфная ассоциация - это когда данный столбец может ссылаться на одну из двух или более родительских таблиц. Невозможно объявить это ограничение в SQL.

Дизайн полиморфных ассоциаций нарушает правила проектирования реляционных баз данных. Я не рекомендую его использовать.

Есть несколько альтернатив:

  • Эксклюзивные дуги: Создание нескольких столбцов внешнего ключа, каждый из которых ссылается на одного родителя. Сделайте так, чтобы ровно один из этих внешних ключей не мог быть NULL.

  • Обратное отношение: Используйте три таблицы типа «многие ко многим», каждая из которых ссылается на Комментарии и соответствующий родительский элемент.

  • Конкретная надтаблица: Вместо неявного суперкласса с комментариями создайте реальную таблицу, на которую ссылается каждая из ваших родительских таблиц. Затем свяжите свои комментарии с этой надтаблицей. Код псевдо-рельсов будет примерно таким (я не являюсь пользователем Rails, поэтому рассматривайте это как руководство, а не буквальный код):

     class Commentable 

Я также освещаю полиморфные ассоциации в своей презентации Практические объектно-ориентированные модели в SQL и в моей книге Антипаттерны SQL: избегая ловушек программирования баз данных .


Re your comment : Да, я знаю, что есть еще один столбец, в котором указано имя таблицы, на которую предположительно указывает внешний ключ. Этот дизайн не поддерживается внешними ключами в SQL.

Что произойдет, например, если вы вставите комментарий и назовете «Видео» в качестве имени родительской таблицы для этого комментария ? Таблица с именем "Видео" не существует. Следует ли прерывать вставку с ошибкой? Какое ограничение нарушается? Откуда СУБД узнает, что этот столбец должен называть существующую таблицу? Как он обрабатывает имена таблиц без учета регистра?

Аналогичным образом, если вы отбрасываете таблицу Events , но у вас есть строки в комментариях , в которых события указаны в качестве их родительских, каков должен быть результат? Следует ли прерывать выпадение таблицы? Следует ли осиротить строки в комментариях ? Следует ли им изменить, чтобы ссылаться на другую существующую таблицу, такую ​​как Статьи ? Имеют ли значения id, которые раньше указывали на События , какой-либо смысл при указании на Статьи ?

Все эти дилеммы связаны с тем, что полиморфные ассоциации зависят от использования данных (т. Е. строковое значение) для ссылки на метаданные (имя таблицы). Это не поддерживается SQL. Данные и метаданные разделены.


Мне трудно обдумать ваше предложение «Конкретной надтаблицы».

  • Определите Commentable как настоящую таблицу SQL, не просто прилагательное в определении вашей модели Rails. Никаких других столбцов не требуется.

     СОЗДАТЬ ТАБЛИЦУ Комментарий (
     id INT AUTO_INCREMENT PRIMARY KEY
    ) ТИП = InnoDB;
    
  • Определите таблицы Статьи , Фотографии и События как «подклассы» Commentable , сделав их первичный ключ также ссылка на внешний ключ Commentable .

     CREATE TABLE Articles (
     id INT PRIMARY KEY, - без автоинкремента
     ИНОСТРАННЫЙ КЛЮЧ (id) ССЫЛКИ Комментарий (id)
    ) ТИП = InnoDB;
    
    - аналогично фотографиям и событиям.
    
  • Определите таблицу комментариев с внешним ключом к Commentable .

     CREATE TABLE Comments (
     id INT ПЕРВИЧНЫЙ КЛЮЧ AUTO_INCREMENT,
     commentable_id INT NOT NULL,
     ИНОСТРАННЫЙ КЛЮЧ (commentable_id) ССЫЛКИ Комментарий (id)
    ) ТИП = InnoDB;
    
  • Если вы хотите создать статью (например), вы также должны создать новую строку в Commentable . То же самое для Фото и События .

     ВСТАВИТЬ В КОММЕНТАРИИ (id) ЗНАЧЕНИЯ (ПО УМОЛЧАНИЮ); - создать новый id 1
    ВСТАВИТЬ В статьи (id, ...) VALUES (LAST_INSERT_ID (), ...);
    
    ВСТАВИТЬ В КОММЕНТАРИИ (id) ЗНАЧЕНИЯ (ПО УМОЛЧАНИЮ) - создать новый id 2
    ВСТАВИТЬ В фотографии (id, ...) VALUES (LAST_INSERT_ID (), ...);
    
    ВСТАВИТЬ В КОММЕНТАРИИ (id) ЗНАЧЕНИЯ (ПО УМОЛЧАНИЮ) - создать новый id 3
    ВСТАВИТЬ В события (id, ...) VALUES (LAST_INSERT_ID (), ...);
    
  • Если вы хотите создать комментарий , используйте значение, которое существует в Commentable .

     INSERT INTO Comments (id, commentable_id, ...)
    ЗНАЧЕНИЯ (ПО УМОЛЧАНИЮ, 2, ...);
    
  • Если вы хотите запросить комментарии к данной фотографии , выполните несколько объединений:

     ВЫБРАТЬ * ИЗ фотографий p ПРИСОЕДИНЯТЬСЯ к комментариям t ВКЛ (p.id = t.id)
    LEFT OUTER JOIN Комментарии c ON (t.id = c.commentable_id)
    ГДЕ p.id = 2;
    
  • Если у вас есть только идентификатор комментария, и вы хотите узнать, для какого ресурса, к которому есть комментарии, это комментарий. Для этого вы можете обнаружить, что для таблицы Commentable полезно указать, на какой ресурс она ссылается.

     SELECT commentable_id, commentable_type FROM Commentable t
    ПРИСОЕДИНЯЙТЕСЬ Комментарии c ON (t.id = c.commentable_id)
    ГДЕ c.id = 42;
    

    Затем вам нужно будет выполнить второй запрос, чтобы получить данные из соответствующей таблицы ресурсов (фотографии, статьи и т. Д.), После того как вы узнаете из commentable_type , к какой таблице присоединиться. Вы не можете сделать это в том же запросе, потому что SQL требует, чтобы таблицы были названы явно; вы не можете присоединиться к таблице, определяемой результатами данных в том же запросе.

По общему признанию, некоторые из этих шагов нарушают соглашения, используемые Rails. Но соглашения Rails неверны в отношении правильного проектирования реляционной базы данных.

168
ответ дан 24 November 2019 в 11:34
поделиться
Другие вопросы по тегам:

Похожие вопросы: