Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI30 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Foreign Key (Внешний ключ): полное руководство
Что такое Foreign Key
Foreign Key (Внешний ключ) — это столбец или набор столбцов в таблице БД, который ссылается на первичный ключ (Primary Key) в другой таблице. Это создаёт связь между таблицами.
Простой пример
-- Таблица users (основная таблица)
CREATE TABLE users (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
email VARCHAR(100) UNIQUE
);
-- Таблица orders (зависимая таблица)
CREATE TABLE orders (
id SERIAL PRIMARY KEY,
user_id INTEGER NOT NULL,
amount DECIMAL(10, 2),
created_at TIMESTAMP DEFAULT NOW(),
-- Внешний ключ: user_id ссылается на id в таблице users
FOREIGN KEY (user_id) REFERENCES users(id)
);
-- user_id в таблице orders - это внешний ключ
Визуально
Таблица: users
┌────┬──────────┬──────────────────┐
│ id │ name │ email │
├────┼──────────┼──────────────────┤
│ 1 │ John │ john@example.com │
│ 2 │ Jane │ jane@example.com │
│ 3 │ Bob │ bob@example.com │
└────┴──────────┴──────────────────┘
↑
│ (Primary Key)
│
│ (Foreign Key ссылка)
Таблица: orders
┌────┬─────────┬────────┬────────────┐
│ id │ user_id │ amount │ created_at │
├────┼─────────┼────────┼────────────┤
│ 1 │ 1 │ 100.00 │ 2024-01-01 │
│ 2 │ 2 │ 250.50 │ 2024-01-02 │
│ 3 │ 1 │ 75.25 │ 2024-01-03 │
└────┴─────────┴────────┴────────────┘
Типы связей через Foreign Keys
1. One-to-Many (Один-ко-многим)
Один пользователь может иметь много заказов:
-- users (1)
id | name
1 | John
2 | Jane
-- orders (Many)
id | user_id | amount
1 | 1 | 100
2 | 1 | 200
3 | 2 | 300
-- John имеет 2 заказа, Jane - 1 заказ
2. Many-to-One (Много-к-одному)
Много заказов ссылаются на одного поставщика:
CREATE TABLE suppliers (
id SERIAL PRIMARY KEY,
name VARCHAR(100)
);
CREATE TABLE products (
id SERIAL PRIMARY KEY,
supplier_id INTEGER,
name VARCHAR(100),
FOREIGN KEY (supplier_id) REFERENCES suppliers(id)
);
3. Many-to-Many (Много-ко-многим)
Много студентов посещают много курсов (нужна промежуточная таблица):
CREATE TABLE students (
id SERIAL PRIMARY KEY,
name VARCHAR(100)
);
CREATE TABLE courses (
id SERIAL PRIMARY KEY,
title VARCHAR(100)
);
-- Промежуточная таблица
CREATE TABLE enrollments (
id SERIAL PRIMARY KEY,
student_id INTEGER NOT NULL,
course_id INTEGER NOT NULL,
FOREIGN KEY (student_id) REFERENCES students(id),
FOREIGN KEY (course_id) REFERENCES courses(id),
-- Уникальная пара (студент не может дважды записаться на курс)
UNIQUE(student_id, course_id)
);
-- Структура:
-- students (1) --> enrollments <-- (1) courses
Ограничения Foreign Key (Constraints)
Foreign Key может иметь разные действия при удалении/обновлении:
-- RESTRICT (или NO ACTION) - по умолчанию
-- Запрещает удалить user, если есть его orders
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE RESTRICT;
-- CASCADE - каскадное удаление
-- Если удалить user, автоматически удалятся все его orders
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;
-- SET NULL - установить NULL
-- Если удалить user, orders будут иметь user_id = NULL
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE SET NULL;
-- SET DEFAULT - установить значение по умолчанию
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE SET DEFAULT;
Пример с разными опциями:
CREATE TABLE products (
id SERIAL PRIMARY KEY,
category_id INTEGER,
supplier_id INTEGER,
name VARCHAR(100),
-- Каскадное удаление для category
FOREIGN KEY (category_id) REFERENCES categories(id) ON DELETE CASCADE,
-- SET NULL для supplier
FOREIGN KEY (supplier_id) REFERENCES suppliers(id) ON DELETE SET NULL
);
Foreign Keys в Node.js/TypeScript
Используя Sequelize (ORM):
// models/User.js
const { DataTypes } = require('sequelize');
module.exports = (sequelize) => {
return sequelize.define('User', {
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true
},
name: {
type: DataTypes.STRING,
allowNull: false
},
email: {
type: DataTypes.STRING,
unique: true
}
});
};
// models/Order.js
const { DataTypes } = require('sequelize');
module.exports = (sequelize) => {
return sequelize.define('Order', {
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true
},
user_id: {
type: DataTypes.INTEGER,
references: {
model: 'Users', // Ссылка на таблицу Users
key: 'id'
},
onDelete: 'CASCADE' // Опция удаления
},
amount: {
type: DataTypes.DECIMAL(10, 2)
}
});
};
// Установление связи
const User = require('./models/User')(sequelize);
const Order = require('./models/Order')(sequelize);
User.hasMany(Order, { foreignKey: 'user_id' });
Order.belongsTo(User, { foreignKey: 'user_id' });
Запрос с JOIN через Foreign Key:
// Получить заказы с информацией о пользователе
const orders = await Order.findAll({
include: [{
model: User,
attributes: ['id', 'name', 'email']
}]
});
// SELECT ... FROM orders JOIN users ON orders.user_id = users.id
Используя TypeORM:
// entities/User.ts
import { Entity, PrimaryGeneratedColumn, Column, OneToMany } from 'typeorm';
import { Order } from './Order';
@Entity('users')
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
@OneToMany(() => Order, (order) => order.user)
orders: Order[];
}
// entities/Order.ts
import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, JoinColumn } from 'typeorm';
import { User } from './User';
@Entity('orders')
export class Order {
@PrimaryGeneratedColumn()
id: number;
@Column()
amount: number;
@ManyToOne(() => User, (user) => user.orders, { onDelete: 'CASCADE' })
@JoinColumn({ name: 'user_id' })
user: User;
@Column()
user_id: number; // Внешний ключ
}
Важные правила для Foreign Keys
1. Тип данных должен совпадать
❌ user_id INTEGER ссылается на users.id BIGINT - ошибка
✅ user_id INTEGER ссылается на users.id INTEGER - хорошо
2. Ссылаемый ключ должен быть UNIQUE или PRIMARY KEY
❌ FOREIGN KEY (user_id) REFERENCES users(name) - ошибка
✅ FOREIGN KEY (user_id) REFERENCES users(id) - PRIMARY KEY
3. Значение в foreign key должно существовать в referenced table
❌ INSERT INTO orders (user_id) VALUES (999) - если пользователя 999 нет
✅ INSERT INTO orders (user_id) VALUES (1) - пользователь 1 существует
4. Foreign Key помогает поддерживать referential integrity
- Нельзя удалить пользователя, если есть его заказы (зависит от ON DELETE)
Advantages (Преимущества)
// Referential Integrity (Целостность ссылок)
// БД гарантирует, что все ссылки валидны
const userId = 999; // пользователя нет
await Order.create({ user_id: userId }); // Ошибка!
// Cascading Operations
// При удалении пользователя автоматически удалятся его заказы
await User.destroy({ where: { id: 1 } }); // Удаляет и его orders
// Query Optimization
// БД может оптимизировать JOIN'ы
const orders = await Order.findAll({
include: [{ model: User }] // Один оптимизированный запрос
});
Disadvantages (Недостатки)
// Performance может пострадать
// Много Foreign Keys = сложные JOIN'ы
// Сложность удаления
// Нужно помнить про каскадное удаление
// Жёсткие связи
// Трудно менять структуру с Foreign Keys
// Микросервисная архитектура
// Foreign Keys не работают между базами разных сервисов
Когда использовать Foreign Keys
✅ Использовать:
- Когда нужна целостность данных
- В реляционных БД (PostgreSQL, MySQL)
- Когда есть одна БД для всего приложения
- Production системы требующие гарантий
❌ НЕ использовать:
- В микросервисной архитектуре (разные БД)
- В NoSQL БД (MongoDB, DynamoDB)
- Когда нужна высокая гибкость
- Когда performance критичен (очень много JOIN'ов)
Выводы
Foreign Key:
- Связывает строки между таблицами
- Обеспечивает referential integrity
- Может иметь разные стратегии удаления (CASCADE, SET NULL, RESTRICT)
- Одна-ко-многим - основная связь
- Многие-ко-многим требуют промежуточной таблицы
- Важны для целостности данных в реляционных БД
- Могут влиять на производительность при неправильном использовании