NodeJS Back-end Инженер

NodeJS Back-end Инженер

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

Для чего нужен Foreign Key?

DatabasesSQLKeys

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

Foreign Key (внешний ключ) — это столбец или набор столбцов, который создаёт связь между двумя таблицами, ссылаясь на PRIMARY KEY другой таблицы. Главная цель — обеспечить ссылочную целостность данных.

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

  • Ссылочная целостность — FK гарантирует, что значение существует в родительской таблице
  • Связь таблиц — FK определяет отношение между родительской и дочерней таблицей
  • Каскадные операции — автоматические действия при удалении/обновлении родительской записи
  • Индексация — СУБД обычно создаёт индекс для FK, ускоряя JOIN

Зачем нужен Foreign Key?

  • Предотвращает создание «сиротских» записей (ссылки на несуществующие данные)
  • Автоматизирует контроль целостности на уровне БД
  • Документирует связи между таблицами в схеме
  • Позволяет использовать каскадное удаление/обновление

Плюсы

  • Надёжная защита целостности данных
  • Не зависит от логики приложения
  • Упрощает понимание структуры БД

Минусы

  • Замедляет операции INSERT/UPDATE/DELETE (дополнительные проверки)
  • Усложняет миграции и массовые операции
  • Требует правильного порядка вставки/удаления данных

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

  • Путают PRIMARY KEY и FOREIGN KEY
  • Не знают о каскадных операциях (ON DELETE CASCADE)
  • Забывают, что FK может содержать NULL (если не указан NOT NULL)

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

Представьте таблицу заказов, где user_id ссылается на пользователя. Без Foreign Key ничто не мешает записать user_id = 9999, даже если такого пользователя не существует. Это создаёт «сиротские» записи — данные, ссылающиеся в никуда.

sql
-- ❌ Без FOREIGN KEY: база примет любое значение
INSERT INTO orders (user_id, total) VALUES (9999, 100.00);
-- Успешно! Но пользователя 9999 не существует...

Foreign Key решает эту проблему, запрещая вставку значений, которые не существуют в родительской таблице.


Базовая теория

Что такое Foreign Key?

Foreign Key (FK, внешний ключ) — это столбец (или группа столбцов), который ссылается на PRIMARY KEY другой таблицы. FK создаёт связь между двумя таблицами и обеспечивает ссылочную целостность (referential integrity).

Терминология

ТерминОписание
Родительская таблицаТаблица с PRIMARY KEY, на которую ссылаются
Дочерняя таблицаТаблица с FOREIGN KEY, которая ссылается
Ссылочная целостностьГарантия существования связанной записи
Каскадные операцииАвтоматические действия при изменении родителя

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

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

Практические примеры

Создание FOREIGN KEY при создании таблицы

sql
-- Родительская таблица
CREATE TABLE users (
    id SERIAL PRIMARY KEY,
    name VARCHAR(100) NOT NULL,
    email VARCHAR(255) UNIQUE NOT NULL
);
 
-- Дочерняя таблица с FK
CREATE TABLE orders (
    id SERIAL PRIMARY KEY,
    user_id INT NOT NULL,
    total DECIMAL(10, 2) NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    CONSTRAINT fk_orders_user
        FOREIGN KEY (user_id) REFERENCES users(id)
);

Проверка ссылочной целостности

sql
-- Вставляем пользователя
INSERT INTO users (name, email) VALUES ('John', 'john@mail.com');
-- id = 1
 
-- ✅ Успешно: пользователь существует
INSERT INTO orders (user_id, total) VALUES (1, 99.99);
 
-- ❌ Ошибка: пользователя с id=999 не существует
INSERT INTO orders (user_id, total) VALUES (999, 50.00);
-- ERROR: insert or update on table "orders" violates foreign key constraint

Каскадные операции

Каскадные операции определяют, что происходит с дочерними записями при удалении или обновлении родительской.

ON DELETE — при удалении родителя

sql
CREATE TABLE orders (
    id SERIAL PRIMARY KEY,
    user_id INT,
    total DECIMAL(10, 2),
    FOREIGN KEY (user_id) REFERENCES users(id)
        ON DELETE CASCADE  -- удалить заказы при удалении пользователя
);

Варианты поведения

ОпцияОписаниеКогда использовать
CASCADEУдалить/обновить связанные записиЗависимые данные (заказы пользователя)
SET NULLУстановить NULLОпциональная связь (автор удалён)
SET DEFAULTУстановить значение по умолчаниюРезервное значение
RESTRICTЗапретить операциюЗащита от случайного удаления
NO ACTIONТо же, что RESTRICTПо умолчанию

Примеры использования

sql
-- При удалении пользователя удаляются все его заказы
FOREIGN KEY (user_id) REFERENCES users(id)
    ON DELETE CASCADE
    ON UPDATE CASCADE;
 
-- Пример
DELETE FROM users WHERE id = 1;
-- Автоматически удалит все заказы с user_id = 1

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

FK может быть NULL

⚠️

FOREIGN KEY не подразумевает NOT NULL. Если связь опциональная, FK может быть NULL.

sql
CREATE TABLE articles (
    id SERIAL PRIMARY KEY,
    title VARCHAR(255) NOT NULL,
    author_id INT,  -- может быть NULL (анонимная статья)
    FOREIGN KEY (author_id) REFERENCES users(id)
);
 
-- ✅ Успешно: статья без автора
INSERT INTO articles (title, author_id) VALUES ('Anonymous Post', NULL);

Составной FOREIGN KEY

sql
-- FK может ссылаться на составной PRIMARY KEY
CREATE TABLE order_items (
    order_id INT,
    product_id INT,
    warehouse_id INT,
    quantity INT,
    PRIMARY KEY (order_id, product_id),
    FOREIGN KEY (warehouse_id, product_id)
        REFERENCES inventory(warehouse_id, product_id)
);

Самоссылающийся FK (Self-referencing)

sql
-- Иерархия сотрудников: каждый может иметь руководителя
CREATE TABLE employees (
    id SERIAL PRIMARY KEY,
    name VARCHAR(100) NOT NULL,
    manager_id INT,
    FOREIGN KEY (manager_id) REFERENCES employees(id)
);
 
-- CEO без руководителя
INSERT INTO employees (name, manager_id) VALUES ('CEO', NULL);
 
-- Менеджер подчиняется CEO
INSERT INTO employees (name, manager_id) VALUES ('Manager', 1);

Порядок операций

🚫

Порядок важен! При INSERT сначала родитель, потом дочерний. При DELETE — наоборот (или используйте CASCADE).

Правильная вставка

sql
-- 1. Сначала родитель
INSERT INTO users (name, email) VALUES ('Alice', 'alice@mail.com');
 
-- 2. Потом дочерний (FK уже существует)
INSERT INTO orders (user_id, total) VALUES (1, 150.00);

Правильное удаление (без CASCADE)

sql
-- 1. Сначала дочерние записи
DELETE FROM orders WHERE user_id = 1;
 
-- 2. Потом родитель
DELETE FROM users WHERE id = 1;

Производительность

АспектВлияние
SELECT/JOIN✅ FK создаёт индекс → быстрые JOIN
INSERT❌ Проверка существования родителя
UPDATE❌ Проверка при изменении FK
DELETE❌ Проверка дочерних записей

Несмотря на небольшое замедление записи, FK критически важен для целостности данных. Это trade-off, который почти всегда оправдан.


Сравнение: PRIMARY KEY vs FOREIGN KEY

АспектPRIMARY KEYFOREIGN KEY
НазначениеИдентификация записиСвязь с другой таблицей
УникальностьОбязательнаНе требуется
NULLЗапрещёнДопустим (если нет NOT NULL)
Количество в таблицеОдинМного
ИндексСоздаётся автоматическиСоздаётся автоматически (в большинстве СУБД)

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

Q: Зачем нужен FOREIGN KEY, если можно делать JOIN без него?

FK обеспечивает ссылочную целостность — гарантию, что связанные данные существуют. Без FK возможны «сиротские» записи.

Q: Что такое ссылочная целостность?

Это гарантия, что значение FOREIGN KEY всегда указывает на существующую запись в родительской таблице.

Q: Когда использовать ON DELETE CASCADE?

Когда дочерние записи не имеют смысла без родителя. Например, элементы корзины при удалении корзины.

Q: Влияет ли FK на производительность?

Да, немного замедляет INSERT/UPDATE/DELETE из-за проверок. Но JOIN ускоряется благодаря автоматическому индексу.

Q: Может ли FOREIGN KEY ссылаться на не PRIMARY KEY?

Да, может ссылаться на любой столбец с UNIQUE constraint.


Источники