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

Ответ

В реляционных базах данных связи (relationships) определяют, как строки одной таблицы соотносятся со строками другой. Существует три фундаментальных типа связей, реализуемых через первичные (PK) и внешние ключи (FK).

1. Один к одному (One-to-One, 1:1)

Одна запись в таблице А связана не более чем с одной записью в таблице Б, и наоборот.

Зачем это нужно? Часто используется для разделения сущности на несколько таблиц по соображениям:

  • Безопасности: Конфиденциальные данные (паспорт) отдельно от основных (пользователь).
  • Производительности: Часто запрашиваемые поля — в одной таблице, редко используемые — в другой.
  • Наследования (Table per Hierarchy): В ORM, например, для реализации наследования.

Реализация в БД: Внешний ключ создается в любой из таблиц, но должен быть также UNIQUE, чтобы гарантировать "один к одному".

CREATE TABLE Users (
    UserId INT PRIMARY KEY,
    Username NVARCHAR(50) NOT NULL
);

CREATE TABLE UserProfiles (
    ProfileId INT PRIMARY KEY,
    UserId INT UNIQUE NOT NULL, -- И FK, и UNIQUE!
    Bio NVARCHAR(MAX),
    CONSTRAINT FK_Profile_User FOREIGN KEY (UserId) REFERENCES Users(UserId)
);

2. Один ко многим (One-to-Many, 1:N)

Самая распространенная связь. Одна запись в таблице А может быть связана со многими записями в таблице Б, но запись в Б связана только с одной записью в А.

Примеры: BlogPosts, CustomerOrders, CountryCities.

Реализация в БД: Внешний ключ создается в таблице со стороны "многих" (таблица Б).

CREATE TABLE Blogs (
    BlogId INT PRIMARY KEY,
    Name NVARCHAR(100) NOT NULL
);

CREATE TABLE Posts (
    PostId INT PRIMARY KEY,
    Title NVARCHAR(200) NOT NULL,
    BlogId INT NOT NULL, -- FK на стороне "многих"
    CONSTRAINT FK_Post_Blog FOREIGN KEY (BlogId) REFERENCES Blogs(BlogId)
);

3. Многие ко многим (Many-to-Many, N:N)

Одна запись в А может быть связана со многими записями в Б, и наоборот.

Примеры: StudentsCourses, ProductsOrders, BooksAuthors.

Реализация в БД: Требуется промежуточная таблица-связка (junction/join table). Эта таблица содержит как минимум два внешних ключа, ссылающихся на первичные ключи связываемых таблиц. Ее составной ключ (PK1+PK2) уникален и предотвращает дублирование связей.

CREATE TABLE Students (
    StudentId INT PRIMARY KEY,
    Name NVARCHAR(100) NOT NULL
);

CREATE TABLE Courses (
    CourseId INT PRIMARY KEY,
    Title NVARCHAR(100) NOT NULL
);

-- Таблица-связка
CREATE TABLE StudentCourses (
    StudentId INT NOT NULL,
    CourseId INT NOT NULL,
    EnrollmentDate DATE,
    PRIMARY KEY (StudentId, CourseId), -- Составной первичный ключ
    CONSTRAINT FK_StudentCourse_Student FOREIGN KEY (StudentId) REFERENCES Students(StudentId),
    CONSTRAINT FK_StudentCourse_Course FOREIGN KEY (CourseId) REFERENCES Courses(CourseId)
);

Важно в ORM (Entity Framework Core): Для связи "многие ко многим" EF Core 5+ позволяет напрямую конфигурировать коллекции без явного создания класса для таблицы-связки, но под капотом она все равно создается.

Ответ 18+ 🔞

Давай разберём эту тему про связи в базах, а то некоторые думают, что это какая-то высшая математика, блядь. На самом деле всё просто, как три рубля, если не усложнять.

Вот смотри, есть три основных типа, как таблицы друг на друга смотрят. Всё крутится вокруг первичных и внешних ключей — это как паспорт и прописка, ёпта.

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

Это когда одной записи в первой таблице строго соответствует не больше одной записи во второй, и наоборот. Как жена и муж в нормальной семье, блядь — у каждого должен быть только один, иначе пиздец.

Зачем это вообще нужно? Ну, например, чтобы от греха подальше. Основные данные пользователя лежат в одной таблице, а его паспортные данные, кредитная история и размер члена — в другой, защищённой. Или чтобы не тащить кучу полей, которые запрашивают раз в пятилетку. В общем, для порядка.

Как делается: Берёшь и в одну из таблиц пихаешь внешний ключ на другую, но обязательно с UNIQUE, чтобы второй раз не прицепилось. Иначе будет уже "один ко многим", а это совсем другая история.

CREATE TABLE Users (
    UserId INT PRIMARY KEY,
    Username NVARCHAR(50) NOT NULL
);

CREATE TABLE UserProfiles (
    ProfileId INT PRIMARY KEY,
    UserId INT UNIQUE NOT NULL, -- Смотри, и FK, и UNIQUE! Чтобы к одному юзеру был один профиль!
    Bio NVARCHAR(MAX),
    CONSTRAINT FK_Profile_User FOREIGN KEY (UserId) REFERENCES Users(UserId)
);

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

Самая популярная связь, встречается на каждом шагу. Одна запись в таблице А может иметь дохуя записей в таблице Б, но каждая запись из Б привязана только к одной из А. Как начальник и его отдел: у начальника много подчинённых, а у каждого подчинённого — один начальник (если, конечно, он не работает на двух хозяев, пидор).

Примеры: Блог и посты, заказчик и его заказы, страна и города.

Как делается: Внешний ключ ставится в таблицу "многих". Всё логично — подчинённый хранит id своего начальника.

CREATE TABLE Blogs (
    BlogId INT PRIMARY KEY,
    Name NVARCHAR(100) NOT NULL
);

CREATE TABLE Posts (
    PostId INT PRIMARY KEY,
    Title NVARCHAR(200) NOT NULL,
    BlogId INT NOT NULL, -- Вот он, FK! Пост знает, к какому блогу приписан.
    CONSTRAINT FK_Post_Blog FOREIGN KEY (BlogId) REFERENCES Blogs(BlogId)
);

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

А вот это уже веселее. Одна запись в А может ссылаться на много записей в Б, и наоборот. Как студенты и курсы: один студент ходит на много курсов, а на один курс записывается много студентов. Или продукты в заказе — полная жопа, если делать напрямую.

Как делается: Тут без промежуточной таблицы-связки — никак, блядь. Она как сводница между двумя другими. В этой таблице-связке сидят минимум два внешних ключа, которые тыкают в исходные таблицы. А её составной первичный ключ (оба FK вместе) не даёт создать одну и ту же связь дважды.

CREATE TABLE Students (
    StudentId INT PRIMARY KEY,
    Name NVARCHAR(100) NOT NULL
);

CREATE TABLE Courses (
    CourseId INT PRIMARY KEY,
    Title NVARCHAR(100) NOT NULL
);

-- А вот и наша сводница
CREATE TABLE StudentCourses (
    StudentId INT NOT NULL,
    CourseId INT NOT NULL,
    EnrollmentDate DATE,
    PRIMARY KEY (StudentId, CourseId), -- Составной ключ. Не даст одному студенту дважды на один курс записаться.
    CONSTRAINT FK_StudentCourse_Student FOREIGN KEY (StudentId) REFERENCES Students(StudentId),
    CONSTRAINT FK_StudentCourse_Course FOREIGN KEY (CourseId) REFERENCES Courses(CourseId)
);

Важный момент для тех, кто пишет на EF Core: Начиная с пятой версии, можно в коде делать вид, что этой таблицы-связки нет, просто объявить коллекции друг у друга. Но не обманывайся — под капотом эта самая сводница всё равно создастся, её не обойти. ORM — он хоть и умный, но не волшебник, ёпта.