Как написать SQL-запрос для вывода пользователей и элементов их корзины с использованием join?
Что проверяют
Знаете ли вы разницу между видами соединений и понимаете ли, что результат запроса потом читает Go-код. Следом почти всегда спрашивают: «А если корзина у пользователя пустая?» и «Как собрать это в структуры без N+1?»
Как отвечать
Сначала уточните схему. Обычно это users, cart_items с полями user_id, product_id, quantity и справочник products:
SELECT u.id, u.name, p.title, ci.quantity
FROM users u
LEFT JOIN cart_items ci ON ci.user_id = u.id
LEFT JOIN products p ON p.id = ci.product_id
ORDER BY u.id;Дальше объясните выбор:
- INNER или LEFT: голый
JOINэто внутреннее соединение, пользователи без корзины пропадут. Нужны все, беритеLEFT JOIN, у пустых корзин колонки справа придутNULL; - фильтр по правой таблице: условие вроде
p.is_active = trueвWHEREпревращаетLEFT JOINобратно во внутренний, его место вON; - чтение в Go:
db.QueryContext(ctx, q), сразуdefer rows.Close(), после цикла обязательноrows.Err(). Колонки, которые бываютNULL, сканируются вsql.NullStringили*string; - сборка структур: строки плоские, пользователь повторяется на каждый товар. Группируете в
map[int64]*Userили, опираясь наORDER BY u.id, дописываете товары к текущему пользователю; - без N+1: один запрос с
JOINвместо запроса корзины в цикле по пользователям. В PostgreSQL можно сразу получить корзину массивом черезjson_aggсGROUP BY u.id; - индексы и параметры: индекс на
cart_items(user_id), значения фильтров только через плейсхолдеры$1, никогда черезfmt.Sprintf.
Где спотыкаются
Пишут голый JOIN и не замечают, что пользователи без товаров исчезли из результата.
Сканируют колонку из правой таблицы в обычный string и получают ошибку Scan на первом же пользователе с пустой корзиной.
Забывают rows.Close() или проверку rows.Err(): соединение не возвращается в пул, а ошибка посреди выборки теряется молча, и функция отдаёт обрезанный список как полный.