Поліморфні зв’язки для найменших

Нещодавно, роблячи черговий функціонал на одному з проектів, я зіткнувся з трохи незвичайними зв'язками в реляційних СУБД, які, як виявилося пізніше, мають хитромудру назву — Поліморфні зв'язки. Що це таке, як і де їх застосовувати, я спробую пояснити у цій статті.

Тема поліморфних зв'язків вже піднімалася не раз на Хабрі («Rails і поліморфні зв'язки», «Поліморфні наскрізні асоціації в Ruby on Rails», «Поліморфні зв'язки»), але піднімалася вона в контексті Ruby, і для тих, хто вже має якийсь досвід у проектуванні БД. Початківцям же (мені було), мало що зрозуміло з тих статей, тому в цій статті я спробую розповісти все на пальцях, абстрагуючись від мови, хіба що трохи зачеплю ORM популярних фреймворків в Інтернеті. Усім зрозумілі звичайні «взаємини» табличок в реляційних БД: один-до-одного, один-до-багатьом, багато-до-багатьом. А якщо не зрозумілі, то вам прості приклади їх реалізації.

Один-до-одного. Одного запису з першої таблиці відповідає лише один запис з другої таблиці. Тут все просто. Найпоширеніший приклад - таблиця user і user_profile (Кожному користувачеві відповідає один профіль).

Багато-багатьом. Зв'язок реалізується, коли одному рядку з однієї таблиці може відповідати кілька записів з іншого та навпаки. Хороший приклад - є таблиця статей (articles), є таблиця тегів (tags), зв'язуються вони через проміжну таблицю (pivot table або junction table) tags_articles, де є article_id, tag_id. Здається, все тут просто і зрозуміло.

Звідки ж взялися якісь поліморфні зв'язки, якщо й так попередні зв'язки цілком логічні і начебто не вимагають доповнень?

Попередні зв'язки (один-до-одного, один-до-багатьом, багато-до-багатьом), створюються для статичнихсутностей з таблиць, куди можна навісити обмеження (constraints) СУБД.

Повернемося наприклад зв'язку одним-багатьом.

Суть поліморфних зв'язків

Поліморфні зв'язки це динамічні зв'язки між таблицями з використанням типу сутності. Щоб було зрозуміло, змінимо трохи наші таблиці і зробимо з-поміж них поліморфні зв'язку.

Наша ще одна таблиця - news:

Поліморфні зв'язки можуть бути реалізовані, і як багато-багатьом.Показувати таблиці з даними не має сенсу, покажу лише приблизну структуру. articles: id - integer text - text

posts: id - integer text - text

tags: id — integer name — string

tags_entities tag_id - integer tag_entity_id - integer tag_entity_type - string (postarticle)

Мінуси поліморфних зв'язків

Не все так ідеально, як могло б здатися на перший погляд. У силу своєї динамічної природи поліморфних зв'язків, між полями таблиць, що не зв'язуються, не можна проставити зв'язки зовнішніх ключів (foreign key) використовуючи СУБД, а тим більше і обмеження (constraints) на зміну або видалення записів. Це, власне, найбільший мінус поліморфних зв'язків. Доведеться, або писати свої тригери (процедури або ще що) для самої СУБД, або, що частіше роблять, перекласти роботу із синхронізації рядків та накладення обмежень між таблицями на ORM та мову програмування.

Другий, менш значний мінус поліморфних зв'язків полягає у типі сутності. Необхідно якось описати якийсь тип, якій таблиці належить. Це може бути не очевидно, якщо, наприклад, назва якоїсь таблиці змінилася або якщо ви задали тип сутності цифрами. Вирішити цю проблему можна, наприклад, створивши окрему таблицю, або прописавши в коді проекту, асоціативниймасив із зіставленням типу та сутності.

Робота ORM з поліморфними зв'язками

Слід сказати, що сучасні фреймворки та їх ORM без особливих складнощів, здатні працювати з даними зв'язками. Наприклад, як уже говорилося вище, Ruby on Rails підтримує їх із коробки. Php-фреймворк Laravel також має у своїй реалізації ORM для таких типів зв'язків зручні методи (morphTo, morphMany і т.д.), а як тип сутності використовує повну назву класу моделі. У фреймворку Yii2, немає з коробки якихось специфічних методів для такого роду зв'язків, але вони можуть бути реалізовані через звичайні методи hasOne, hasMany з додатковими умовами при прописуванні зв'язків.

З усього сказаного вище, новачкам варто звернути увагу на те, коли використовувати поліморфні зв'язки. Не варто їх пхати праворуч і ліворуч, з проекту до проекту, тільки тому, що це круто. Потрібно трохи прикинути наперед, а чи з'являться завтра нові таблиці, нові сутності з однаковим функціоналом та вимогами, які можна було б винести та зробити динамічними, та виходячи від відповіді проектувати свої БД.

А у нас тут можна отримати грант на тестовий період Яндекс.Хмари. Варто лише у полі «секретний пароль» запровадити «Хабр»