NodeJS Back-end Инженер

NodeJS Back-end Инженер

Роадмап навыков для прокачки

Какие типы связей между таблицами существуют?

DatabasesSQLRelationship

Основная идея

В реляционных базах данных существует три типа связей между таблицами: один-к-одному (One-to-One), один-ко-многим (One-to-Many) и многие-ко-многим (Many-to-Many). Каждый тип определяет, сколько записей одной таблицы может быть связано с записями другой.

Ключевые аспекты

  • One-to-One (1:1) — одна запись связана ровно с одной записью (пользователь ↔ профиль)
  • One-to-Many (1:N) — одна запись связана со многими (пользователь → заказы)
  • Many-to-Many (M:N) — многие записи связаны со многими (студенты ↔ курсы)
  • Промежуточная таблица — используется для реализации Many-to-Many связей

Как определить тип связи

  • Задайте вопрос: «Сколько B может быть у одного A? Сколько A может быть у одного B?»
  • 1:1 — по одному с каждой стороны
  • 1:N — один с одной стороны, много с другой
  • M:N — много с обеих сторон

Плюсы понимания типов связей

  • Правильное проектирование схемы БД
  • Оптимальная структура без избыточности
  • Корректные JOIN-запросы

Частые ошибки на собеседованиях

  • Не знают, как реализовать Many-to-Many (нужна промежуточная таблица)
  • Путают направление связи 1:N (FK всегда в «многих»)
  • Используют 1:1 там, где достаточно одной таблицы

Введение и проблематика

При проектировании базы данных важно правильно определить тип связи между сущностями. От этого зависит структура таблиц, расположение FOREIGN KEY и способ выполнения запросов.

Тип связи определяется бизнес-логикой, а не техническими ограничениями. Вопрос «сколько заказов может быть у пользователя?» — это бизнес-правило.

Три типа связей

  1. One-to-One (1:1) — один к одному
  2. One-to-Many (1:N) — один ко многим
  3. Many-to-Many (M:N) — многие ко многим

One-to-One (Один к одному)

Описание

Каждая запись в таблице A связана ровно с одной записью в таблице B, и наоборот.

Примеры из реальной жизни

  • Пользователь ↔ Паспорт
  • Сотрудник ↔ Рабочее место
  • Страна ↔ Столица
  • Пользователь ↔ Детальный профиль

Визуализация

erDiagram USERS ||--|| USER_PROFILES : "has one" USERS { int id PK varchar email varchar password_hash } USER_PROFILES { int id PK int user_id FK,UK text bio varchar avatar_url date birth_date }

Реализация в SQL

sql
CREATE TABLE users (
    id SERIAL PRIMARY KEY,
    email VARCHAR(255) UNIQUE NOT NULL,
    password_hash VARCHAR(255) NOT NULL
);
 
CREATE TABLE user_profiles (
    id SERIAL PRIMARY KEY,
    user_id INT UNIQUE NOT NULL,  -- UNIQUE гарантирует 1:1
    bio TEXT,
    avatar_url VARCHAR(500),
    birth_date DATE,
    FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
);

Ключ к 1:1 — это UNIQUE constraint на FOREIGN KEY. Без него связь превратится в 1:N.

Когда использовать 1:1?

  • Разделение по частоте доступа — редко используемые данные выносятся в отдельную таблицу
  • Безопасность — чувствительные данные в отдельной таблице с другими правами доступа
  • Наследование сущностей — базовая таблица + специализированные расширения
⚠️

Если данные всегда запрашиваются вместе — лучше объединить в одну таблицу. Лишний JOIN без причины — антипаттерн.


One-to-Many (Один ко многим)

Описание

Одна запись в таблице A связана с несколькими записями в таблице B, но каждая запись в B связана только с одной записью в A.

Это самый распространённый тип связи.

Примеры из реальной жизни

  • Пользователь → Заказы (у пользователя много заказов)
  • Автор → Книги (автор написал несколько книг)
  • Категория → Товары (в категории много товаров)
  • Пост → Комментарии (у поста много комментариев)

Визуализация

erDiagram USERS ||--o{ ORDERS : "places" USERS { int id PK varchar name varchar email } ORDERS { int id PK int user_id FK decimal total timestamp created_at }

Реализация в SQL

sql
CREATE TABLE users (
    id SERIAL PRIMARY KEY,
    name VARCHAR(100) NOT NULL,
    email VARCHAR(255) UNIQUE NOT NULL
);
 
CREATE TABLE orders (
    id SERIAL PRIMARY KEY,
    user_id INT NOT NULL,
    total DECIMAL(10, 2) NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (user_id) REFERENCES users(id)
);

FOREIGN KEY всегда находится на стороне «многих». В связи User → Orders FK будет в таблице orders.

Запросы

sql
-- Все заказы пользователя
SELECT * FROM orders WHERE user_id = 1;
 
-- Пользователи с количеством заказов
SELECT u.name, COUNT(o.id) as order_count
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
GROUP BY u.id, u.name;

Many-to-Many (Многие ко многим)

Описание

Каждая запись в таблице A может быть связана с несколькими записями в таблице B, и каждая запись в B может быть связана с несколькими записями в A.

Примеры из реальной жизни

  • Студенты ↔ Курсы (студент на многих курсах, на курсе много студентов)
  • Товары ↔ Теги (товар имеет много тегов, тег у многих товаров)
  • Авторы ↔ Книги (соавторство)
  • Пользователи ↔ Роли (пользователь может иметь несколько ролей)

Визуализация

erDiagram STUDENTS ||--o{ STUDENT_COURSES : "enrolls" COURSES ||--o{ STUDENT_COURSES : "has" STUDENTS { int id PK varchar name varchar email } STUDENT_COURSES { int student_id PK,FK int course_id PK,FK date enrolled_at varchar grade } COURSES { int id PK varchar title int credits }

Реализация через промежуточную таблицу

🚫

Many-to-Many нельзя реализовать напрямую двумя таблицами. Нужна промежуточная таблица (junction table, pivot table, linking table).

sql
-- Основные таблицы
CREATE TABLE students (
    id SERIAL PRIMARY KEY,
    name VARCHAR(100) NOT NULL,
    email VARCHAR(255) UNIQUE NOT NULL
);
 
CREATE TABLE courses (
    id SERIAL PRIMARY KEY,
    title VARCHAR(255) NOT NULL,
    credits INT DEFAULT 3
);
 
-- Промежуточная таблица
CREATE TABLE student_courses (
    student_id INT NOT NULL,
    course_id INT NOT NULL,
    enrolled_at DATE DEFAULT CURRENT_DATE,
    grade VARCHAR(2),
    PRIMARY KEY (student_id, course_id),  -- составной PK
    FOREIGN KEY (student_id) REFERENCES students(id) ON DELETE CASCADE,
    FOREIGN KEY (course_id) REFERENCES courses(id) ON DELETE CASCADE
);

Запросы для Many-to-Many

sql
-- Все курсы студента
SELECT c.title, sc.grade
FROM courses c
JOIN student_courses sc ON c.id = sc.course_id
WHERE sc.student_id = 1;
 
-- Все студенты на курсе
SELECT s.name, sc.enrolled_at
FROM students s
JOIN student_courses sc ON s.id = sc.student_id
WHERE sc.course_id = 5;
 
-- Записать студента на курс
INSERT INTO student_courses (student_id, course_id)
VALUES (1, 5);
 
-- Удалить связь
DELETE FROM student_courses
WHERE student_id = 1 AND course_id = 5;

Сравнение типов связей

АспектOne-to-OneOne-to-ManyMany-to-Many
ПримерUser ↔ ProfileUser → OrdersStudents ↔ Courses
FK расположениеВ любой таблице (+ UNIQUE)В таблице «многих»В промежуточной таблице
Количество таблиц223 (+ junction)
JOIN-запросПростойПростойДвойной JOIN
Частота использованияРедкоОчень частоЧасто

Как определить тип связи?

Задайте два вопроса:

  1. Сколько B может быть у одного A?
  2. Сколько A может быть у одного B?
  • Один + Один = 1:1
  • Один + Много = 1:N
  • Много + Много = M:N

Пограничные кейсы

⚠️

Самосвязь (Self-referencing): Таблица может ссылаться сама на себя.

sql
-- Иерархия сотрудников (1:N самосвязь)
CREATE TABLE employees (
    id SERIAL PRIMARY KEY,
    name VARCHAR(100) NOT NULL,
    manager_id INT,
    FOREIGN KEY (manager_id) REFERENCES employees(id)
);
 
-- Друзья в соцсети (M:N самосвязь)
CREATE TABLE friendships (
    user_id INT NOT NULL,
    friend_id INT NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    PRIMARY KEY (user_id, friend_id),
    FOREIGN KEY (user_id) REFERENCES users(id),
    FOREIGN KEY (friend_id) REFERENCES users(id)
);

Вопросы интервьюера

Q: Как реализовать Many-to-Many связь?

Через промежуточную таблицу (junction table) с двумя FOREIGN KEY на обе связываемые таблицы.

Q: Где размещать FOREIGN KEY в связи One-to-Many?

Всегда на стороне «многих». В связи User → Orders FK находится в таблице orders.

Q: Чем отличается 1:1 от 1:N на уровне схемы?

В 1:1 на FOREIGN KEY добавляется UNIQUE constraint, гарантирующий уникальность.

Q: Может ли промежуточная таблица содержать дополнительные поля?

Да, часто содержит метаданные связи: дата создания, роль, статус и т.д.


Источники