Ответ
Связь «многие-ко-многим» реализуется через промежуточную таблицу (junction/join table), которая содержит внешние ключи на обе связанные таблицы. Я использовал этот подход в проектах с MySQL и PostgreSQL.
Базовый пример SQL:
-- Основные таблицы
CREATE TABLE authors (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(100) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE books (
id INT PRIMARY KEY AUTO_INCREMENT,
title VARCHAR(200) NOT NULL,
isbn VARCHAR(13) UNIQUE,
published_year INT
);
-- Промежуточная таблица
CREATE TABLE author_book (
author_id INT NOT NULL,
book_id INT NOT NULL,
-- Дополнительные поля, если нужно
contribution_type ENUM('main', 'co-author', 'editor') DEFAULT 'main',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (author_id, book_id),
FOREIGN KEY (author_id) REFERENCES authors(id) ON DELETE CASCADE,
FOREIGN KEY (book_id) REFERENCES books(id) ON DELETE CASCADE,
INDEX idx_book_id (book_id) -- Для обратных запросов
);
Запросы для работы со связью:
-- Найти все книги автора
SELECT b.*
FROM books b
JOIN author_book ab ON b.id = ab.book_id
WHERE ab.author_id = 1;
-- Найти всех авторов книги
SELECT a.*
FROM authors a
JOIN author_book ab ON a.id = ab.author_id
WHERE ab.book_id = 5;
-- Добавить связь
INSERT INTO author_book (author_id, book_id, contribution_type)
VALUES (1, 5, 'main');
-- Удалить связь
DELETE FROM author_book
WHERE author_id = 1 AND book_id = 5;
Реализация в ORM:
// Laravel Eloquent
class Author extends Model {
public function books() {
return $this->belongsToMany(Book::class, 'author_book')
->withPivot('contribution_type')
->withTimestamps();
}
}
class Book extends Model {
public function authors() {
return $this->belongsToMany(Author::class, 'author_book')
->withPivot('contribution_type');
}
}
// Использование
$author = Author::find(1);
$author->books()->attach(5, ['contribution_type' => 'main']);
$books = $author->books()->where('published_year', '>', 2000)->get();
Ключевые моменты:
- Составной первичный ключ
(author_id, book_id)гарантирует уникальность связи ON DELETE CASCADEавтоматически удаляет связи при удалении записей- Промежуточная таблица может содержать дополнительные атрибуты связи (метаданные)
- Индексы на обоих внешних ключах критически важны для производительности
- В сложных случаях можно использовать триггеры для поддержания целостности