Работали ли вы с HAVING в SQL
Что проверяют
Понимаете ли вы порядок выполнения SQL-запроса и умеете ли фильтровать группы, а не строки. Для Go-разработчика это ещё вопрос о том, где считаются агрегаты: в базе или в памяти сервиса. Ответ «выберу все строки и посчитаю циклом» слышат часто, и он же обычная причина деградации сервиса на объёме.
Как отвечать
Скажите главное: WHERE отсеивает строки до группировки, HAVING отсеивает уже готовые группы. Логический порядок такой: FROM, WHERE, GROUP BY, вычисление агрегатов, HAVING, SELECT, ORDER BY, LIMIT. Отсюда следствия: в WHERE нельзя написать count(*), а в HAVING в PostgreSQL нельзя сослаться на алиас из SELECT, его там ещё нет.
select product_id, sum(quantity) as total
from sales
where sold_at >= $1
group by product_id
having sum(quantity) > $2;Условие по обычной колонке держите в WHERE, так база сгруппирует меньше строк. HAVING без GROUP BY тоже валиден, вся выборка считается одной группой.
Дальше добавьте специфику Go: count приходит как int64, а тип sum зависит от колонки, по int он укрупняется до bigint, а по bigint становится numeric, который драйвер не положит в число без явного приведения. Агрегат по пустому множеству даёт NULL, поэтому либо coalesce(sum(x), 0), либо sql.NullInt64 в структуре для сканирования.
Где спотыкаются
Переносят в HAVING условия, которым там не место. Фильтр по дате или статусу работает и после группировки, но база успевает сгруппировать лишнее. План меняется заметно, стоит посмотреть explain analyze и поднять условие выше.
Второе: тянут строки в сервис и агрегируют в Go. На тестовых данных быстро, на проде это гигабайты трафика и память воркера. База сделает то же по индексу и вернёт десяток строк.
Третье: путают count(*) и count(column). Второй не считает NULL, поэтому условия having count(col) > 0 и having count(*) > 0 отбирают разные группы, и на колонке с пропусками отчёт молча расходится с ожиданием.