Модуль 18

Звʼязки: Many-to-Many

Ось готовий урок, написаний у стилі CS50, адаптований для української аудиторії.


🎓 CS50: Зв’язки Many-to-Many (Багато-до-Багатьох)

Привіт, друзі! Це CS50 (умовно 😉), і сьогодні ми розберемо одну з найцікавіших архітектурних головоломок у базах даних.


1. 🔥 Вступ: Хаос у Spotify

Уявіть, що ви — головний інженер Spotify. Ваше завдання: спроєктувати базу даних для пісень та плейлистів.

Давайте поміркуємо. У вас є таблиця Songs (Пісні). У вас є таблиця Playlists (Плейлисти).

І тут виникає проблема: 1. Одна пісня (наприклад, "Bohemian Rhapsody") може бути у багатьох плейлистах («Рок хіти», «Караоке», «Улюблене»). 2. Один плейлист («Рок хіти») містить багато пісень.

Це і є Many-to-Many (Багато-до-Багатьох).

А тепер скажіть мені, як це записати в базу? Ви ж не створите в таблиці Songs колонку playlist_id? Тоді пісня зможе належати лише одному плейлисту. Це нікуди не годиться.

Або, можливо, ви запишете в таблицю плейлиста список ID пісень через кому: "1, 55, 99, 102"? 😱 Ні! Будь ласка, ніколи так не робіть! Це повільно, це неможливо нормально фільтрувати, і це пекло для підтримки.

Якщо ми не можемо зв’язати їх напряму... нам потрібен посередник. Міст. Сьогодні ми навчимося будувати ці мости.


2. 🧠 Теоретична база: Магія "Третьої Таблиці"

Отже, запам'ятайте головне правило цього уроку:

У реляційних базах даних неможливо реалізувати зв'язок Many-to-Many напряму.

Натомість ми розбиваємо його на два зв'язки One-to-Many, використовуючи проміжну таблицю.

Як це працює "під капотом"?

Уявіть собі це як журнал реєстрації. У нас є Студенти і Курси. 1. Студент не тримає список курсів у кишені. 2. Курс не пише імена студентів на стінах аудиторії. 3. Є окремий Журнал записів, де кожен рядок — це факт зв'язку: * "Студент ID 5 записався на Курс ID 10". * "Студент ID 5 записався на Курс ID 12".

Словничок (що треба знати):

  1. Junction Table (Сполучна таблиця) — та сама "третя таблиця", міст. Вона часто називається поєднанням імен двох основних, наприклад, Student_Courses.
  2. Foreign Keys (Зовнішні ключі) — ця таблиця складається переважно з ID. Вона каже: "Я беру ID звідти і ID звідси".
  3. Composite Primary Key (Складений ключ) — часто унікальним ідентифікатором у такій таблиці є пара (student_id, course_id). Чому? Бо студент не може двічі записатися на один і той самий курс.

3. 🧪 Приклади: Від теорії до коду

Давайте побудуємо систему для Інтернет-магазину. Сутності: Замовлення (Orders) та Товари (Products). Одне замовлення може містити багато товарів. Один товар може бути в багатьох замовленнях.

Крок 1: Створюємо основні таблиці

CREATE TABLE Products (
    id SERIAL PRIMARY KEY,
    name VARCHAR(100),
    price DECIMAL(10, 2)
);

CREATE TABLE Orders (
    id SERIAL PRIMARY KEY,
    customer_name VARCHAR(100),
    created_at TIMESTAMP DEFAULT NOW()
);

Крок 2: Будуємо міст (Junction Table)

Питання до вас: Як ми назвемо цю таблицю? Зазвичай це OrderItems або Orders_Products.

CREATE TABLE OrderItems (
    order_id INT REFERENCES Orders(id),    -- Посилання на замовлення
    product_id INT REFERENCES Products(id), -- Посилання на товар
    quantity INT DEFAULT 1,                -- Додаткова інфо! (скільки штук)
    PRIMARY KEY (order_id, product_id)     -- Унікальна пара
);

Бачите? Ця таблиця не просто пов'язує. Вона може зберігати деталі зв'язку, наприклад quantity (кількість).

Крок 3: Наповнюємо даними

-- Додамо товари
INSERT INTO Products (name, price) VALUES ('Ноутбук', 1000), ('Мишка', 50), ('Клавіатура', 100);

-- Створимо замовлення для "Олега"
INSERT INTO Orders (customer_name) VALUES ('Олег'); -- припустимо, отримало ID 1

-- А тепер магія зв'язку: Олег купив Ноутбук (ID 1) і Мишку (ID 2)
INSERT INTO OrderItems (order_id, product_id, quantity) VALUES 
(1, 1, 1), -- Замовлення 1 містить Товар 1
(1, 2, 1); -- Замовлення 1 містить Товар 2

Крок 4: Дістаємо дані (JOIN)

Якщо я запитаю базу: "Покажи мені чек Олега", запит виглядатиме так:

SELECT Orders.id, Products.name, Products.price
FROM Orders
JOIN OrderItems ON Orders.id = OrderItems.order_id
JOIN Products ON OrderItems.product_id = Products.id
WHERE Orders.customer_name = 'Олег';

Результат: | id | name | price | |---|---|---| | 1 | Ноутбук | 1000 | | 1 | Мишка | 50 |

Ми пройшли через міст і зібрали дані з обох берегів! 🌉


4. 🛠 Практична частина

Час забруднити руки кодом. Ось ваші завдання:

🔹 Завдання 1: Школа (Розігрів) Створіть схему для Вчителів та Класів. Один вчитель вчить багато класів (5-А, 5-Б), один клас вчать багато вчителів. Напишіть SQL для створення трьох таблиць.

🔹 Завдання 2: Кінотеатр (Реальний кейс) Є таблиці Movies (Фільми) та Actors (Актори). Створіть таблицю Cast (Акторський склад). Умова: Актор може грати у фільмі певна роль (наприклад, "Головний герой", "Злодій"). Додайте поле role у сполучну таблицю.

🔹 Завдання 3: Виправ помилку новачка Студент спроєктував базу для блогу. У нього є таблиця Posts і колонка tags (теги), де написано: "news, tech, ukraine". Напишіть (словами або псевдокодом), як переробити це на правильну структуру Many-to-Many.

🔹 Завдання 4: Міні-кейс "Соціальна мережа" Як реалізувати "Підписників" (Followers)? Користувач підписується на Користувача. Підказка: Вам потрібні дві таблиці чи одна таблиця і одна сполучна, де обидва ключі ведуть на ту саму таблицю Users? Спробуйте намалювати це.

🔹 Питання "А що, якщо..." Що станеться із записами в таблиці OrderItems, якщо ми видалимо товар із таблиці Products? Як захистити дані від "битых посилань"? (Гугліть: ON DELETE CASCADE).


5. 💡 Мислення як у розробника

Як відрізнити новачка від профі в цій темі?

  1. Новачок намагається запхнути масиви даних в одну комірку (JSON або текст через кому). Він боїться створювати зайві таблиці.
  2. Профі знає: Дані — дешеві, структура — безцінна. Профі завжди створює третю таблицю, бо знає, що завтра бізнес скаже: "А давайте додамо дату, коли студент записався на курс?".
    • У схемі Many-to-Many це просто додати колонку в сполучну таблицю.
    • У схемі "через кому" — це переписувати всю базу.

Порада з практики: Сполучна таблиця — це не просто "технічний клей". Часто це окрема сутність бізнесу. * User + Event = Registration (Реєстрація) * User + Product = Review (Відгук - якщо один юзер може оцінювати багато товарів)

Завжди шукайте іменник, який ховається між двома сутностями.


6. 🧩 Підсумок

Отже, що ми маємо? * Ми зрозуміли, що прямий зв'язок Many-to-Many — це ілюзія. * Ми навчилися будувати "мости" (Junction Tables). * Ми побачили, як зберігати додаткові дані прямо на цьому "мосту" (кількість товару, роль актора).

Тепер ви можете спроєктувати базу даних практично для будь-чого: від Instagram до Uber.

Що далі? У вас тепер купа таблиць і зв'язків. Як зробити так, щоб пошук у мільйонах записів займав мілісекунди, а не хвилини? Наступна тема: Індекси (Indexing) та B-Trees. 🌳

А поки що... це був CS50. Успіхів у коді!