Пошук уроків, статей та іншого контенту
Побудуйте ER-модель і визначте кардинальність зв’язків між сутностями перед створенням таблиць.
Перед створенням таблиць потрібно визначити:
які об’єкти існують у предметній області;
які властивості має кожен об’єкт;
як об’єкти пов’язані між собою;
скільки екземплярів однієї сутності може відповідати екземплярам іншої.
Таку структуру описує ER-модель — модель сутностей і зв’язків.
Наприклад, для невеликого інтернет-магазину можна виділити:
customers — покупці;
products — товари;
orders — замовлення;
order_items — позиції замовлення.
На етапі моделювання ми ще не створюємо таблиці. Спочатку описуємо логічну структуру даних.
Сутність — це об’єкт предметної області, про який потрібно зберігати дані.
У базі даних сутність зазвичай стає таблицею.
Наприклад, сутність Customer може мати такі атрибути:
ідентифікатор;
ім’я;
адресу електронної пошти;
дату реєстрації.
Сутність Product
ідентифікатор;
назву;
ціну;
опис.
Кожен екземпляр сутності повинен мати спосіб однозначної ідентифікації. Зазвичай для цього використовують первинний ключ.
Наприклад:
Customer
- id
- name
- emailПоле id ідентифікує конкретного покупця, а name та email описують його властивості.
Для пошуку сутностей проаналізуйте опис задачі та поставте запитання:
Про які об’єкти потрібно зберігати дані?
Чи має об’єкт власний ідентифікатор?
Чи існує об’єкт незалежно від інших?
Чи потрібно зберігати багато екземплярів цього об’єкта?
Наприклад, у вимозі:
Покупець створює замовлення. Замовлення містить товари. Один товар може входити до багатьох замовлень.
Можна виділити такі сутності:
покупець;
замовлення;
товар;
позиція замовлення.
Позиція замовлення потрібна для зберігання додаткових даних про участь товару в конкретному замовленні, наприклад кількості та ціни на момент покупки.
Зв’язок описує, як сутності пов’язані між собою.
Приклади зв’язків:
покупець створює замовлення;
замовлення містить позиції;
позиція замовлення посилається на товар.
Під час моделювання потрібно визначити дві характеристики зв’язку:
кардинальність — скільки об’єктів може бути пов’язано;
обов’язковість — чи зв’язок є обов’язковим або необов’язковим.
Одному екземпляру першої сутності відповідає не більше одного екземпляра другої сутності.
Наприклад:
User 1 —— 1 UserProfileОдин користувач має один профіль, і один профіль належить одному користувачеві.
На практиці такий зв’язок потрібно використовувати лише тоді, коли частини даних справді мають бути окремими сутностями. Наприклад, профіль може зберігатися окремо через відмінні правила доступу або рідше використання.
У таблицях зв’язок 1:1 часто реалізують зовнішнім ключем, для якого встановлено обмеження UNIQUE.
Одному екземпляру першої сутності може відповідати багато екземплярів другої.
Customer 1 —— N OrderОдин покупець може мати багато замовлень, але кожне замовлення належить одному покупцеві.
У реляційній базі даних зовнішній ключ розміщують на стороні «багато». Тобто таблиця orders містить customer_id.
customers
- id
- name
orders
- id
- customer_id
- created_atПоле orders.customer_id посилається на customers.id.
Кожен екземпляр першої сутності може бути пов’язаний із багатьма екземплярами другої, і навпаки.
Order N —— M ProductОдне замовлення може містити багато товарів. Один товар може входити до багатьох замовлень.
Безпосередньо зберегти такий зв’язок одним зовнішнім ключем не можна. Для нього створюють проміжну сутність, наприклад OrderItem.
Order 1 —— N OrderItem N —— 1 ProductТепер зв’язок M:N представлено двома зв’язками 1:N.
Сутність OrderItem може мати такі атрибути:
order_id;
product_id;
quantity;
unit_price.
Кардинальність також описує мінімальну кількість пов’язаних об’єктів.
Розглянемо зв’язок між покупцем і замовленням:
Customer 1 —— N OrderДля покупця замовлення може бути необов’язковим: новий покупець ще не обов’язково має замовлення.
Для замовлення покупець може бути обов’язковим: замовлення не має сенсу без покупця.
Це можна описати так:
Customer 0..N —— 1 Order0..N — покупець може мати від нуля до багатьох замовлень;
1 — кожне замовлення має рівно одного покупця.
Відповідно, у таблиці orders поле customer_id буде NOT NULL.
Для зв’язку між замовленням і позиціями часто використовують таку модель:
Order 1 —— 1..N OrderItemЦе означає, що замовлення повинно мати щонайменше одну позицію, а позицій може бути багато.
Звичайні зовнішні ключі гарантують, що позиція посилається на існуюче замовлення. Але самі по собі вони не гарантують, що кожне замовлення має хоча б одну позицію. Таке правило зазвичай додатково реалізують у логіці застосунку або спеціальними операціями в базі даних.
Логічну модель можна подати текстом:
Customer
- id
- name
- email
Product
- id
- name
- price
Order
- id
- customer_id
- created_at
OrderItem
- order_id
- product_id
- quantity
- unit_priceЗв’язки:
Customer 1 —— 0..N Order
Order 1 —— 1..N OrderItem
Product 1 —— 0..N OrderItemВисновки:
один покупець може не мати замовлень або мати багато замовлень;
кожне замовлення належить одному покупцеві;
кожна позиція належить одному замовленню;
кожна позиція посилається на один товар;
один товар може ще не входити до жодного замовлення або входити до багатьох.
Після визначення сутностей і зв’язків можна створити таблиці.
-- Видаляємо таблиці у правильному порядку через залежності
DROP TABLE IF EXISTS order_items;
DROP TABLE IF EXISTS orders;
DROP TABLE IF EXISTS products;
DROP TABLE IF EXISTS customers;
CREATE TABLE customers (
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
name text NOT NULL,
email text NOT NULL UNIQUE
);
CREATE TABLE products (
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
name text NOT NULL,
price numeric(10, 2) NOT NULL CHECK (price >= 0)
);
CREATE TABLE orders (
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
customer_id bigint NOT NULL,
created_at timestamptz NOT NULL DEFAULT now(),
CONSTRAINT orders_customer_fk
FOREIGN KEY (customer_id)
REFERENCES customers (id)
);
CREATE TABLE order_items (
order_id bigint NOT NULL,
product_id bigint NOT NULL,
quantity integer NOT NULL CHECK (quantity > 0),
unit_price numeric(10, 2) NOT NULL CHECK (unit_price >= 0),
PRIMARY KEY (order_id, product_id),
CONSTRAINT order_items_order_fk
FOREIGN KEY (order_id)
REFERENCES orders (id)
ON DELETE CASCADE,
CONSTRAINT order_items_product_fk
FOREIGN KEY (product_id)
REFERENCES products (id)
);
INSERT INTO customers (name, email)
VALUES ('Олена Коваль', 'olena@example.com');
INSERT INTO products (name, price)
VALUES
('Клавіатура', 2500.00),
('Миша', 1200.00);
INSERT INTO orders (customer_id)
VALUES (1);
INSERT INTO order_items (order_id, product_id, quantity, unit_price)
VALUES
(1, 1, 1, 2500.00),
(1, 2, 2, 1200.00);
SELECT
o.id AS order_id,
c.name AS customer_name,
p.name AS product_name,
oi.quantity,
oi.unit_price
FROM orders AS o
JOIN customers AS c ON c.id = o.customer_id
JOIN order_items AS oi ON oi.order_id = o.id
JOIN products AS p ON p.id = oi.product_id
ORDER BY o.id, p.id;У цій моделі:
customers.id, products.id та orders.id — первинні ключі;
orders.customer_id — зовнішній ключ на customers;
order_items.order_id — зовнішній ключ на orders;
order_items.product_id — зовнішній ключ на products;
складений первинний ключ (order_id, product_id) не дозволяє додати один товар до одного замовлення двічі окремими рядками.
Поле unit_price зберігає ціну товару саме в момент замовлення. Якщо ціна товару в products зміниться пізніше, історія замовлення залишиться правильною.
Перед створенням таблиць використовуйте такий порядок:
Прочитайте вимоги та випишіть основні об’єкти.
Перетворіть об’єкти на сутності.
Для кожної сутності визначте атрибути.
Додайте унікальний ідентифікатор.
Визначте зв’язки між сутностями.
Для кожного зв’язку встановіть кардинальність.
Визначте, чи є кожна сторона зв’язку обов’язковою.
Перетворіть зв’язки 1:N на зовнішні ключі.
Перетворіть зв’язки M:N на проміжні сутності.
Лише після цього створюйте таблиці.
Погано:
orders
- id
- product_ids = "1,4,7"Таку структуру складно перевіряти, фільтрувати та змінювати. Для зв’язку замовлень і товарів потрібно створити таблицю order_items.
Для зв’язку «один покупець — багато замовлень» зовнішній ключ має бути в orders, а не в customers.
orders.customer_idСаме сторона «багато» зберігає посилання на сторону «один».
Якщо товар може входити до багатьох замовлень, а замовлення може містити багато товарів, одного поля product_id у orders буде недостатньо. Потрібна таблиця order_items.
Якщо для об’єкта потрібно зберігати власні дані та зв’язки, це, ймовірно, окрема сутність.
Наприклад, OrderItem — не просто атрибут замовлення. Він має кількість, ціну та посилання на товар.
Якщо замовлення завжди повинно мати покупця, customer_id не має бути nullable:
customer_id bigint NOT NULLЯкщо не визначити це правило, база дозволить створити замовлення без покупця.
Не потрібно створювати окрему сутність для кожного атрибута. Ім’я покупця — це атрибут Customer, а не окрема сутність Name, якщо для імені не потрібно зберігати власні дані та зв’язки.
ER-модель описує сутності, їхні атрибути та зв’язки.
Сутність зазвичай стає таблицею, а її екземпляр — рядком.
Основні типи кардинальності: 1:1, 1:N і M:N.
У зв’язку 1:N зовнішній ключ розміщують на стороні «багато».
Зв’язок M:N реалізують через проміжну сутність.
Мінімальна кардинальність показує, чи є зв’язок обов’язковим.
ER-модель потрібно визначити до створення таблиць і зовнішніх ключів.