Пример использования внешних ключей для отношений многие ко многим
Что проверяют
Спроектируете ли связь без подсказки: связующая таблица, ключ из двух колонок, поведение при удалении. Дальше обычно уводят в производительность: какие индексы нужны и как читать такие данные из Go без запроса на каждую строку.
Как отвечать
Связь многие ко многим это отдельная таблица с двумя внешними ключами:
create table student_courses (
student_id bigint not null references students(id) on delete cascade,
course_id bigint not null references courses(id) on delete restrict,
enrolled_at timestamptz not null default now(),
primary key (student_id, course_id)
);
create index on student_courses (course_id);Что стоит проговорить вслух:
- составной первичный ключ сразу даёт уникальность пары, то есть студент не запишется на курс дважды;
- PostgreSQL не создаёт индекс под внешний ключ сам, а первичный ключ помогает выборке по первой колонке, поэтому под
course_idнужен отдельный индекс, иначе удаление курса перечитает всю таблицу; - поведение при удалении выбирается осознанно: каскад для связи, запрет для справочника;
- как только у связи появляются свои атрибуты (дата записи, роль, оценка), это уже полноценная сущность со своей логикой.
Со стороны Go: тянуть одним запросом с соединением и группировать по идентификатору в памяти либо двумя запросами, где второй берёт связи через where course_id = any($1).
Где спотыкаются
Добавляют связующей таблице суррогатный идентификатор и забывают уникальное ограничение на пару. Дубликаты появляются тихо, а потом соединение размножает строки в отчётах.
Не индексируют вторую колонку. Пока таблица маленькая, всё быстро, а на росте выборка «кто на курсе» и каскадное удаление начинают сканировать её целиком.
Полагаются на ORM и не смотрят в сгенерированный SQL. Автоматическая подгрузка связей превращается в запрос на каждую строку, и страница на пару сотен записей делает сотни запросов.