Как найти пользователей с суммой покупок больше 5000 рублей до бана и вывести их по одному на строку в SQL?
Что проверяют
Умеете ли вы соединить две таблицы, отфильтровать строки до агрегации и группы после неё. Главная ловушка в словах «до бана»: что делать с пользователями, которых не банили. Следом часто просят выполнить запрос из Python и вывести результат.
Как отвечать
Сначала уточните схему и смысл. Допустим, есть users(id, banned_at) и purchases(user_id, amount, created_at). Спросите, нужны ли только забаненные или для остальных считать все покупки. Затем запрос:
SELECT u.id
FROM users u
JOIN purchases p ON p.user_id = u.id
WHERE u.banned_at IS NOT NULL
AND p.created_at < u.banned_at
GROUP BY u.id
HAVING SUM(p.amount) > 5000
ORDER BY u.id;Если учитывать и тех, кого не банили, условие меняется на (u.banned_at IS NULL OR p.created_at < u.banned_at).
Проговорите ключевые моменты:
WHEREпротивHAVING:WHEREотсекает покупки до группировки,HAVINGпроверяет уже посчитанную сумму. Агрегат вWHEREписать нельзя;- «по одному на строку»:
GROUP BY u.idсам даёт одну строку на пользователя,DISTINCTсверху не нужен; - деньги: сумма хранится в
NUMERICили целым числом в копейках, не воfloat; - индексы: составной индекс
purchases(user_id, created_at)помогает и соединению, и фильтру по дате.
Из Python запрос выполняется через psycopg или sqlite3, порог передаётся параметром (%s или ?), а не форматированием строки. Вывод по одному на строку: for (user_id,) in cur: print(user_id).
Где спотыкаются
Пишут p.created_at < u.banned_at и не замечают, что при banned_at IS NULL сравнение даёт NULL, и не забаненные пользователи молча выпадают. Иногда это верно, но это нужно сказать вслух.
Ставят условие на сумму в WHERE или фильтруют дату в HAVING: в первом случае ошибка, во втором сумма считается по всем покупкам.
Подставляют порог f-строкой прямо в SQL. На собеседовании это читается как привычка, из которой вырастают SQL-инъекции.