Ось готовий урок, написаний у стилі 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".
Словничок (що треба знати):
- Junction Table (Сполучна таблиця) — та сама "третя таблиця", міст. Вона часто називається поєднанням імен двох основних, наприклад,
Student_Courses. - Foreign Keys (Зовнішні ключі) — ця таблиця складається переважно з ID. Вона каже: "Я беру ID звідти і ID звідси".
- 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. 💡 Мислення як у розробника
Як відрізнити новачка від профі в цій темі?
- Новачок намагається запхнути масиви даних в одну комірку (JSON або текст через кому). Він боїться створювати зайві таблиці.
- Профі знає: Дані — дешеві, структура — безцінна. Профі завжди створює третю таблицю, бо знає, що завтра бізнес скаже: "А давайте додамо дату, коли студент записався на курс?".
- У схемі Many-to-Many це просто додати колонку в сполучну таблицю.
- У схемі "через кому" — це переписувати всю базу.
Порада з практики: Сполучна таблиця — це не просто "технічний клей". Часто це окрема сутність бізнесу. * User + Event = Registration (Реєстрація) * User + Product = Review (Відгук - якщо один юзер може оцінювати багато товарів)
Завжди шукайте іменник, який ховається між двома сутностями.
6. 🧩 Підсумок
Отже, що ми маємо? * Ми зрозуміли, що прямий зв'язок Many-to-Many — це ілюзія. * Ми навчилися будувати "мости" (Junction Tables). * Ми побачили, як зберігати додаткові дані прямо на цьому "мосту" (кількість товару, роль актора).
Тепер ви можете спроєктувати базу даних практично для будь-чого: від Instagram до Uber.
Що далі? У вас тепер купа таблиць і зв'язків. Як зробити так, щоб пошук у мільйонах записів займав мілісекунди, а не хвилини? Наступна тема: Індекси (Indexing) та B-Trees. 🌳
А поки що... це був CS50. Успіхів у коді!