Поліморфний зв'язок - одна колонка посилається на рядки різних таблиць, а друга колонка каже, на яку:
comments: id | commentable_type | commentable_id | body
1 | App\Models\Post | 42 | ...
2 | App\Models\Video | 42 | ...
У Laravel це morphTo / morphMany - зручно: один механізм коментарів, тегів, вкладень для будь-яких моделей.
Проблеми з погляду бази:
- Немає зовнішнього ключа. База не може гарантувати, що
commentable_id = 42справді існує в таблиці, зазначеній уcommentable_type. Видалили пост - коментарі лишилися «сиротами»; каскадне видалення - лише кодом. - Тип - рядок з іменем класу застосунку. Перейменували модель чи простір імен - дані в базі зламалися (у Laravel рятує
Relation::enforceMorphMap()з короткими псевдонімами). - З'єднання незручні: не можна зробити один
JOIN- лише окремі для кожного типу,UNIONчиCASE. - Індекс потрібен складений
(commentable_type, commentable_id), і він більший через рядок типу.
Альтернативи:
1. Окремі колонки на кожен тип з перевіркою, що заповнена рівно одна:
CREATE TABLE comments (
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
post_id bigint REFERENCES posts (id) ON DELETE CASCADE,
video_id bigint REFERENCES videos (id) ON DELETE CASCADE,
body text NOT NULL,
CHECK (num_nonnulls(post_id, video_id) = 1)
);
Справжні зовнішні ключі й каскади. Підходить, коли типів кілька й їх перелік стабільний.
2. Окремі проміжні таблиці: post_comments, video_comments - сам коментар без зв'язку, а зв'язок - у таблиці для кожного типу.
3. Спільний «батьківський» запис: таблиця commentable_entities, на яку посилаються і пости, і відео, а коментарі посилаються на неї. Елегантно, але складніше.
Коли поліморфні зв'язки нормальні: «легкі» універсальні речі (теги, лайки, журнал активності), де цілісність не критична, а типів багато й їх кількість зростає. Для критичних даних (платежі, документи) - краще варіанти зі справжніми ключами.