> Какая связь между таблицами людей и адресов в базе данных (Go)

Уровень: senior · Роль: backend · Язык: Go · Категория: Технические вопросы

Компании: Wildberries

Стек: Go

> Пример ответа

Короткий ответ

Связь между таблицами людей и адресов - это отношение "один ко многим" (one-to-many): один человек может иметь несколько адресов (регистрация, фактический, рабочий), но каждый адрес принадлежит одному человеку. В реляционной модели это реализуется через внешний ключ (foreign key) в таблице адресов, ссылающийся на первичный ключ таблицы людей. Альтернативный вариант - связь "многие ко многим" через промежуточную таблицу, если адрес может быть общим для нескольких людей (например, семья).

Подробное объяснение

В классической схеме:

  • Таблица people содержит первичный ключ id.
  • Таблица addresses содержит колонку person_id как внешний ключ, ссылающийся на people.id.

Это нормализованная модель (3NF), где адрес - зависимая сущность. Каждый адрес хранится отдельно, что позволяет хранить историю адресов, добавлять типы (registration, actual, delivery) и избегать дублирования данных.

Если бизнес-требование допускает, что один адрес используется несколькими людьми (например, члены семьи), используется связь many-to-many через таблицу person_addresses с полями person_id и address_id. Это даёт гибкость, но усложняет запросы и требует дополнительных проверок уникальности.

Выбор между one-to-many и many-to-many зависит от домена. Для большинства систем (CRM, e-commerce) one-to-many достаточно и проще в поддержке. В Go-проектах с ORM (GORM, sqlx) эта связь описывается через HasMany или ManyToMany соответственно.

На практике

При проектировании схемы важно определить:

  • Может ли человек иметь несколько адресов одновременно? Если да - one-to-many.
  • Может ли один адрес быть привязан к нескольким людям? Если да - many-to-many.
  • Нужна ли история адресов? Тогда добавляем valid_from/valid_to и не удаляем записи физически.

В Go-приложениях слой доступа к данным обычно скрывает join-запросы за репозиториями. Например, метод GetUserWithAddresses(ctx, userID) выполняет два запроса (или один с LEFT JOIN) и собирает структуру с вложенным слайсом адресов.

Также стоит подумать об индексах: внешний ключ addresses.person_id должен быть проиндексирован для быстрых выборок по человеку. При удалении человека нужно решить политику - ON DELETE CASCADE или ON DELETE SET NULL (если адрес может существовать без владельца).

Пример кода

GO
type Person struct {
ID int64 `db:"id"`
Name string `db:"name"`
Addresses []Address `db:"-"`
}
type Address struct {
ID int64 `db:"id"`
PersonID int64 `db:"person_id"`
Street string `db:"street"`
City string `db:"city"`
}
// Получение человека с адресами (два запроса)
func GetPersonWithAddresses(ctx context.Context, db *sql.DB, personID int64) (*Person, error) {
var p Person
err := db.GetContext(ctx, &p, "SELECT id, name FROM people WHERE id = $1", personID)
if err != nil {
return nil, err
}
err = db.SelectContext(ctx, &p.Addresses,
"SELECT id, person_id, street, city FROM addresses WHERE person_id = $1", personID)
if err != nil {
return nil, err
}
return &p, nil
}

Как отвечать на собеседовании

Начните с прямого ответа: "обычно это one-to-many". Затем уточните, что выбор зависит от требований, и приведите пример, когда нужна many-to-many. Покажите понимание нормализации и внешних ключей. Упомяните практические детали: индексы, каскадное удаление, загрузка связанных данных в Go. Если интервьюер спрашивает про ORM - опишите, как маппятся HasMany и ManyToMany. Не углубляйтесь в излишние детали, пока не попросят.

Что проверяет интервьюер

  • Понимание реляционной модели и типов связей.
  • Умение обосновать выбор схемы под бизнес-требования.
  • Знание практических аспектов: внешние ключи, индексы, join-запросы.
  • Опыт работы с Go и типичными паттернами доступа к БД (repository, sqlx, GORM).
  • Способность рассуждать о trade-off между нормализацией и производительностью.

Типичные ошибки

  • Ответ "многие ко многим" без обоснования - в большинстве случаев это неверно.
  • Игнорирование вопроса об истории адресов или типах адресов.
  • Предложение хранить адреса в JSON-поле - это антипаттерн для нормализованной схемы.
  • Забывают про индексы на внешних ключах.
  • В Go-коде делают N+1 запросов вместо одного join или двух структурированных запросов.

> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?

Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью