> Напиши SQL запрос для выбора всех чатов пользователя с именем Вася (Go)

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

Компании: Ozon

Стек: Go

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

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

SQL
SELECT c.*
FROM chats c
JOIN chat_participants cp ON cp.chat_id = c.id
JOIN users u ON u.id = cp.user_id
WHERE u.name = 'Вася';

Этот запрос выбирает все чаты, где Вася является участником. Используются три таблицы: users, chat_participants (связка many-to-many) и chats. Если схема другая - например, поле user_id прямо в chats - запрос упрощается до SELECT * FROM chats WHERE user_id = (SELECT id FROM users WHERE name = 'Вася').

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

В типичной схеме мессенджера чаты и пользователи связаны через промежуточную таблицу chat_participants, так как отношение many-to-many: один пользователь может быть во многих чатах, один чат - содержать многих пользователей.

Запрос работает так:

  • JOIN chat_participants связывает чаты с записями об участии.
  • JOIN users связывает участников с пользователями.
  • WHERE u.name = 'Вася' фильтрует только те строки, где имя совпадает.

Важно: если имя не уникально, запрос вернёт чаты всех пользователей с именем Вася. Для точности лучше фильтровать по id пользователя, а не по имени.

На практике

В реальном коде на Go с использованием database/sql запрос обычно параметризуется:

GO
rows, err := db.Query(`
SELECT c.id, c.title, c.created_at
FROM chats c
JOIN chat_participants cp ON cp.chat_id = c.id
JOIN users u ON u.id = cp.user_id
WHERE u.name = $1
`, "Вася")

Имя пользователя передаётся как параметр - это защищает от SQL-инъекций. Также стоит добавить индекс на users.name и chat_participants.user_id для производительности.

Если нужно получить чаты с количеством участников или последним сообщением - добавляются LEFT JOIN и агрегатные функции.

Пример кода

Полный пример на Go:

GO
type Chat struct {
ID int64
Title string
CreatedAt time.Time
}
func GetChatsByUserName(db *sql.DB, name string) ([]Chat, error) {
rows, err := db.Query(`
SELECT c.id, c.title, c.created_at
FROM chats c
JOIN chat_participants cp ON cp.chat_id = c.id
JOIN users u ON u.id = cp.user_id
WHERE u.name = $1
`, name)
if err != nil {
return nil, err
}
defer rows.Close()
var chats []Chat
for rows.Next() {
var ch Chat
if err := rows.Scan(&ch.ID, &ch.Title, &ch.CreatedAt); err != nil {
return nil, err
}
chats = append(chats, ch)
}
return chats, rows.Err()
}

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

Начни с уточнения схемы: спроси, есть ли таблица chat_participants или связь напрямую. Это покажет понимание предметной области. Затем предложи запрос с JOIN и объясни каждый шаг. Упомяни про индексы и параметризацию. Если спросят про производительность - скажи, что при больших объёмах данных стоит фильтровать по user_id, а не по имени. Если спросят про альтернативы - упомяни EXISTS вместо JOIN для избежания дубликатов.

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

  • Понимание SQL JOIN и связей many-to-many.
  • Умение проектировать запрос под конкретную схему.
  • Внимание к деталям: уникальность имени, индексы, параметризация.
  • Способность объяснять выбор подхода и trade-off.

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

  • Забыть про промежуточную таблицу и написать WHERE user_id = ... напрямую.
  • Не учитывать, что имя может быть неуникальным - вернутся лишние чаты.
  • Использовать SELECT * без необходимости - лучше перечислить нужные колонки.
  • Не параметризовать запрос - риск SQL-инъекции.
  • Игнорировать индексы на колонках, участвующих в JOIN и WHERE.

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

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