← Назад к вопросам

Что такое внешний ключ?

1.0 Junior🔥 161 комментариев
#Базы данных и SQL

Комментарии (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)
  • Одна-ко-многим - основная связь
  • Многие-ко-многим требуют промежуточной таблицы
  • Важны для целостности данных в реляционных БД
  • Могут влиять на производительность при неправильном использовании
Что такое внешний ключ? | PrepBro