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

Какие знаешь виды связей в БД?

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

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Какие знаешь виды связей в БД?

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

1. Отношение "Один ко многим" (One-to-Many)

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

Пример: Один автор → много книг

-- Таблица authors
CREATE TABLE authors (
    id SERIAL PRIMARY KEY,
    name VARCHAR(100) NOT NULL
);

-- Таблица books
CREATE TABLE books (
    id SERIAL PRIMARY KEY,
    title VARCHAR(100) NOT NULL,
    author_id INT NOT NULL,
    FOREIGN KEY (author_id) REFERENCES authors(id)
);

На Python (SQLAlchemy):

from sqlalchemy import Column, Integer, String, ForeignKey
from sqlalchemy.orm import relationship
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()

class Author(Base):
    __tablename__ = 'authors'
    
    id = Column(Integer, primary_key=True)
    name = Column(String(100), nullable=False)
    books = relationship('Book', back_populates='author')

class Book(Base):
    __tablename__ = 'books'
    
    id = Column(Integer, primary_key=True)
    title = Column(String(100), nullable=False)
    author_id = Column(Integer, ForeignKey('authors.id'), nullable=False)
    author = relationship('Author', back_populates='books')

# Использование
author = Author(name="Isaac Asimov")
book1 = Book(title="Foundation", author=author)
book2 = Book(title="I, Robot", author=author)

# Навигация
print(author.books)  # [book1, book2]
print(book1.author.name)  # Isaac Asimov

2. Отношение "Много ко многим" (Many-to-Many)

Записи в таблице A могут быть связаны с множеством записей в таблице B, и наоборот.

Пример: Много студентов → много курсов (студент может ходить на несколько курсов, на курсе много студентов)

-- Таблица students
CREATE TABLE students (
    id SERIAL PRIMARY KEY,
    name VARCHAR(100) NOT NULL
);

-- Таблица courses
CREATE TABLE courses (
    id SERIAL PRIMARY KEY,
    title VARCHAR(100) NOT NULL
);

-- Связующая таблица (junction table)
CREATE TABLE student_courses (
    student_id INT NOT NULL,
    course_id INT NOT NULL,
    enrollment_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    PRIMARY KEY (student_id, course_id),
    FOREIGN KEY (student_id) REFERENCES students(id),
    FOREIGN KEY (course_id) REFERENCES courses(id)
);

На Python (SQLAlchemy):

from sqlalchemy import Table, Column, Integer, String, DateTime, ForeignKey
from sqlalchemy.orm import relationship
from sqlalchemy.ext.declarative import declarative_base
from datetime import datetime

Base = declarative_base()

# Связующая таблица
student_courses = Table(
    'student_courses',
    Base.metadata,
    Column('student_id', Integer, ForeignKey('students.id'), primary_key=True),
    Column('course_id', Integer, ForeignKey('courses.id'), primary_key=True),
    Column('enrollment_date', DateTime, default=datetime.utcnow)
)

class Student(Base):
    __tablename__ = 'students'
    
    id = Column(Integer, primary_key=True)
    name = Column(String(100), nullable=False)
    courses = relationship(
        'Course',
        secondary=student_courses,
        back_populates='students'
    )

class Course(Base):
    __tablename__ = 'courses'
    
    id = Column(Integer, primary_key=True)
    title = Column(String(100), nullable=False)
    students = relationship(
        'Student',
        secondary=student_courses,
        back_populates='courses'
    )

# Использование
student = Student(name="Alice")
course1 = Course(title="Python")
course2 = Course(title="Django")

student.courses.append(course1)
student.courses.append(course2)

print(student.courses)  # [course1, course2]
print(course1.students)  # [student]

3. Отношение "Один к одному" (One-to-One)

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

Пример: Один пользователь → один профиль

CREATE TABLE users (
    id SERIAL PRIMARY KEY,
    username VARCHAR(50) UNIQUE NOT NULL
);

CREATE TABLE profiles (
    id SERIAL PRIMARY KEY,
    user_id INT UNIQUE NOT NULL,  -- UNIQUE важен для One-to-One!
    bio TEXT,
    avatar_url VARCHAR(255),
    FOREIGN KEY (user_id) REFERENCES users(id)
);

На Python (SQLAlchemy):

from sqlalchemy import Column, Integer, String, ForeignKey, UniqueConstraint
from sqlalchemy.orm import relationship
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()

class User(Base):
    __tablename__ = 'users'
    
    id = Column(Integer, primary_key=True)
    username = Column(String(50), unique=True, nullable=False)
    profile = relationship(
        'Profile',
        uselist=False,  # One-to-one: только один профиль
        back_populates='user',
        cascade='all, delete-orphan'  # Удалить профиль если удален юзер
    )

class Profile(Base):
    __tablename__ = 'profiles'
    
    id = Column(Integer, primary_key=True)
    user_id = Column(Integer, ForeignKey('users.id'), unique=True, nullable=False)
    bio = Column(String(500))
    avatar_url = Column(String(255))
    user = relationship('User', back_populates='profile')

# Использование
user = User(username="john_doe")
profile = Profile(bio="Software Engineer", avatar_url="https://...")
user.profile = profile

print(user.profile.bio)  # Software Engineer

4. Полиморфные отношения (Polymorphic Relations)

Одна таблица может быть связана с несколькими разными таблицами одновременно.

Пример: Комментарии могут быть к постам, видео, фото

CREATE TABLE comments (
    id SERIAL PRIMARY KEY,
    text TEXT NOT NULL,
    commentable_type VARCHAR(50),  -- 'Post', 'Video', 'Photo'
    commentable_id INT,            -- ID поста/видео/фото
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE posts (
    id SERIAL PRIMARY KEY,
    title VARCHAR(100) NOT NULL
);

CREATE TABLE videos (
    id SERIAL PRIMARY KEY,
    url VARCHAR(255) NOT NULL
);

На Python (SQLAlchemy с однотаблицовым наследованием):

from sqlalchemy import Column, Integer, String, ForeignKey
from sqlalchemy.orm import relationship
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()

class Comment(Base):
    __tablename__ = 'comments'
    
    id = Column(Integer, primary_key=True)
    text = Column(String(255), nullable=False)
    commentable_type = Column(String(50))
    commentable_id = Column(Integer)

class Post(Base):
    __tablename__ = 'posts'
    
    id = Column(Integer, primary_key=True)
    title = Column(String(100), nullable=False)

class Video(Base):
    __tablename__ = 'videos'
    
    id = Column(Integer, primary_key=True)
    url = Column(String(255), nullable=False)

# Использование (упрощенно)
post = Post(title="Hello World")
comment = Comment(text="Great post!", commentable_type="Post", commentable_id=post.id)

5. Самоссылающиеся отношения (Self-Referencing)

Таблица содержит ссылку на саму себя.

Пример: Иерархия сотрудников (менеджер → подчиненные)

CREATE TABLE employees (
    id SERIAL PRIMARY KEY,
    name VARCHAR(100) NOT NULL,
    manager_id INT,
    FOREIGN KEY (manager_id) REFERENCES employees(id)
);

На Python (SQLAlchemy):

from sqlalchemy import Column, Integer, String, ForeignKey
from sqlalchemy.orm import relationship
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()

class Employee(Base):
    __tablename__ = 'employees'
    
    id = Column(Integer, primary_key=True)
    name = Column(String(100), nullable=False)
    manager_id = Column(Integer, ForeignKey('employees.id'))
    
    # Отношение к менеджеру
    manager = relationship(
        'Employee',
        remote_side=[id],
        back_populates='subordinates',
        uselist=False
    )
    
    # Отношение к подчиненным
    subordinates = relationship(
        'Employee',
        back_populates='manager',
        remote_side=[manager_id]
    )

# Использование
boss = Employee(name="Alice")
worker1 = Employee(name="Bob", manager=boss)
worker2 = Employee(name="Charlie", manager=boss)

print(boss.subordinates)  # [worker1, worker2]
print(worker1.manager.name)  # Alice

Каскадное удаление (Cascade Delete)

# ON DELETE CASCADE: при удалении автора удалятся все его книги
class Author(Base):
    __tablename__ = 'authors'
    
    id = Column(Integer, primary_key=True)
    name = Column(String(100))
    books = relationship(
        'Book',
        back_populates='author',
        cascade='all, delete-orphan'  # Удалить книги при удалении автора
    )

class Book(Base):
    __tablename__ = 'books'
    
    id = Column(Integer, primary_key=True)
    title = Column(String(100))
    author_id = Column(Integer, ForeignKey('authors.id'))
    author = relationship('Author', back_populates='books')

# При удалении автора все его книги удалятся
db.delete(author)  # Удалятся и author, и все его books

Таблица сравнения

Тип связи      | Примеры                    | UNIQUE | Many Side
----------------|----------------------------|--------|-------------
One-to-Many    | Автор → Книги             | Нет    | Книга
Many-to-Many   | Студенты ↔ Курсы          | Нет    | Обе
One-to-One     | Пользователь → Профиль   | Да     | Профиль
Self-Reference | Сотрудник → Менеджер      | Нет    | Сотрудник

Избегаемые ошибки

❌ Неправильно: Many-to-Many в одной таблице

class StudentCourse(Base):
    __tablename__ = 'student_courses'
    
    student_id = Column(Integer)
    course_id = Column(Integer)
    # Нет PRIMARY KEY и FOREIGN KEY!

✅ Правильно:

class StudentCourse(Base):
    __tablename__ = 'student_courses'
    
    student_id = Column(Integer, ForeignKey('students.id'), primary_key=True)
    course_id = Column(Integer, ForeignKey('courses.id'), primary_key=True)

Заключение

Основные виды связей в БД:

  1. One-to-Many — самая частая, используй Foreign Key в "Many" таблице
  2. Many-to-Many — нужна связующая (junction) таблица
  3. One-to-One — UNIQUE constraint на Foreign Key
  4. Polymorphic — для гибкости, но следи за консистентностью
  5. Self-Reference — для иерархии и графов

Правильная нормализация БД начинается с правильного выбора типа связи!