ER-модель: сущности, атрибуты, связи
Чертёж базы данных, который рисуют до первой строчки SQL
Прежде чем строить дом, архитектор рисует чертёж: где стены, где двери, как комнаты соединяются. С базой данных так же. Если сразу начать писать CREATE TABLE, легко получить путаницу: данные дублируются, связи теряются, а исправлять потом дорого. Поэтому сначала рисуют ER-модель — схему того, какие объекты живут в базе и как они связаны. Это язык, на котором аналитик, программист и заказчик договариваются ещё до кода.
ER-модель (Entity–Relationship, «сущность–связь») — это концептуальное описание данных в терминах сущностей (объектов), их атрибутов (свойств) и связей между ними. Это чертёж предметной области, ещё не привязанный к конкретной СУБД.
Три кирпича модели
Вся ER-модель собирается из трёх понятий. Удобно сразу держать в голове, во что каждое из них превратится в готовой базе.
| Понятие ER | Что это | Во что превращается в SQL | Пример |
|---|---|---|---|
| Сущность | Класс объектов, о которых храним данные | Таблица | Клиент, Заказ, Товар |
| Атрибут | Свойство сущности | Столбец | имя клиента, цена товара |
| Связь | Как сущности относятся друг к другу | Внешний ключ (или таблица-связка) | клиент делает заказ |
Отдельный объект сущности называют экземпляром: «Клиент» — сущность, а «Анна Петрова» — её экземпляр. Один атрибут обычно назначают ключом — он уникально опознаёт экземпляр (например, cust_id). Подчёркнутый или помеченный PK атрибут в описании — это и есть будущий первичный ключ.
Пример: интернет-магазин
Возьмём знакомую всем задачу — маленький магазин. Нам нужно хранить покупателей, их заказы и товары. Выпишем сущности и атрибуты в виде таблицы — это и есть текстовое представление ER-модели, с которым обходятся без всякой графики.
| Сущность | Атрибуты (PK выделен) | Смысл |
|---|---|---|
| Клиент (Customer) | cust_id (PK), имя, город, email | тот, кто покупает |
| Заказ (Order) | order_id (PK), дата, сумма, cust_id (FK) | факт покупки в конкретный день |
| Товар (Product) | product_id (PK), название, цена | что продаём |
Уже видно две связи: клиент делает заказы, а заказ содержит товары. Но связи бывают разной «ёмкости» — и это самое важное в модели.
Кардинальность: сколько с кем
Кардинальность отвечает на вопрос: скольким экземплярам одной сущности может соответствовать экземпляр другой? Бывает три базовых вида.
| Тип | Читается как | Пример | Как реализуют в SQL |
|---|---|---|---|
| 1:1 | один к одному | Сотрудник — его служебный пропуск | FK на любой стороне + UNIQUE |
| 1:N | один ко многим | Один клиент — много заказов | FK на стороне «многих» (в Заказе) |
| M:N | многие ко многим | Заказ содержит много товаров, товар встречается во многих заказах | отдельная таблица-связка |
Самая частая — 1:N. У клиента может быть ноль, один или сотня заказов, но каждый заказ принадлежит ровно одному клиенту. Поэтому внешний ключ cust_id ставят в таблицу заказов, а не наоборот: хранить список заказов внутри клиента означало бы класть много значений в одну ячейку, что в реляционной модели запрещено.
Связь M:N напрямую в таблицах не выразить — её всегда разбивают на две связи 1:N через промежуточную таблицу. Для пары «Заказ–Товар» заводят таблицу order_items со столбцами order_id и product_id (и заодно количеством). Так связь «многие ко многим» превращается в две аккуратные связи 1:N.
Таблица связей нашего магазина
| Связь | Сущности | Кардинальность | Реализация |
|---|---|---|---|
| делает | Клиент → Заказ | 1:N | FK cust_id в таблице Заказ |
| содержит | Заказ ↔ Товар | M:N | таблица-связка order_items |
От чертежа к таблицам
Теперь самое приятное: ER-описание почти дословно переводится в SQL. Связь 1:N между Клиентом и Заказом — это столбец cust_id в таблице заказов с ограничением REFERENCES. Запустите блок ниже: мы создаём обе таблицы, заполняем их и одним JOIN собираем заказы вместе с именами клиентов — ровно так, как обещала модель.
CREATE TABLE customer (
cust_id INTEGER PRIMARY KEY,
cust_name TEXT NOT NULL,
city TEXT
);
CREATE TABLE "order" (
order_id INTEGER PRIMARY KEY,
order_date TEXT,
amount INTEGER,
cust_id INTEGER REFERENCES customer(cust_id)
);
INSERT INTO customer (cust_id, cust_name, city) VALUES
(1, 'Анна Петрова', 'Москва'),
(2, 'Иван Сидоров', 'Казань'),
(3, 'Ольга Кузнецова', 'Самара');
INSERT INTO "order" (order_id, order_date, amount, cust_id) VALUES
(101, '2026-01-10', 2500, 1),
(102, '2026-01-11', 990, 1),
(103, '2026-02-02', 4300, 2);
SELECT c.cust_name AS клиент,
o.order_id AS заказ,
o.amount AS сумма
FROM "order" AS o
JOIN customer AS c ON o.cust_id = c.cust_id
ORDER BY o.order_id;В результате три строки: у Анны два заказа, у Ивана один, а Ольга в выборку не попала — она пока ничего не купила. Это и есть смысл связи 1:N в действии: JOIN идёт по тому самому внешнему ключу, что мы заложили в чертёж. Поменяйте JOIN на LEFT JOIN — и Ольга появится с пустыми заказами.
Как читать чужое ER-описание
Когда вам дают текстовое ER-описание, читайте его как историю предметной области. Полезный приём — проговаривать связь в обе стороны: «один клиент делает много заказов» и «один заказ принадлежит одному клиенту». Если обе фразы звучат естественно — кардинальность определена верно. Ключи (PK) подсказывают, по чему искать экземпляр, а внешние ключи (FK) показывают, куда тянется связь.
Частые ошибки
- Путают сущность и атрибут. «Город» чаще атрибут клиента, а не отдельная сущность — не плодите таблицы там, где хватает столбца.
- Ставят FK не на ту сторону. В связи 1:N внешний ключ всегда живёт у «многих»:
cust_idв Заказе, а неorder_idв Клиенте.- Пытаются сделать M:N одной таблицей. «Многие ко многим» невозможно без таблицы-связки — иначе придётся дублировать строки.
- Забывают про ноль. «Один клиент — много заказов» включает и случай «ноль заказов»: новый клиент может ещё ничего не купить.
Коротко
- ER-модель — это чертёж базы: сущности (будущие таблицы), атрибуты (столбцы) и связи между ними.
- Каждую сущность опознаёт ключ (PK); связь между таблицами держит внешний ключ (FK).
- Кардинальность бывает 1:1, 1:N и M:N. 1:N реализуют через FK у «многих», M:N — через таблицу-связку.
- Хорошее ER-описание дословно переводится в
CREATE TABLE, а его связи — вJOIN.