В каких случаях работает агрегирующая функция count в SQL
Что проверяют
Знаете ли вы, как COUNT обращается с NULL, пустой выборкой и JOIN, а не только синтаксис. Для Go-разработчика следом часто идёт вопрос, во что сканировать результат и когда вместо подсчёта нужен EXISTS.
Как отвечать
- *`COUNT()
**: считает строки целиком, даже состоящие из однихNULL. В PostgreSQLCOUNT(1)` даёт то же самое, заметной разницы в скорости нет; COUNT(col): считает только строки, гдеcolнеNULL;COUNT(DISTINCT col): уникальные непустые значения;- с
GROUP BY: по строке на группу, а условие на результат пишется вHAVING COUNT(*) > 1, не вWHERE; - условный подсчёт: в PostgreSQL
COUNT(*) FILTER (WHERE status = 'paid'), переносимый вариантCOUNT(CASE WHEN status = 'paid' THEN 1 END); - оконный:
COUNT(*) OVER ()кладёт общее число строк рядом с каждой строкой страницы и избавляет от второго запроса при пагинации.
Важная деталь: агрегат без GROUP BY всегда возвращает ровно одну строку. На пустой таблице COUNT даст 0, а SUM даст NULL. В Go это значит: db.QueryRowContext(ctx, q).Scan(&n) для COUNT безопасен, а для SUM нужен sql.NullInt64 или COALESCE(SUM(x), 0) прямо в запросе. COUNT в PostgreSQL возвращает bigint, поэтому переменная int64.
Про производительность: в PostgreSQL из-за MVCC нет готового счётчика строк, COUNT(*) по большой таблице читает её целиком или обходит индекс через index-only scan. Для приблизительного числа подходит reltuples из pg_class, а для проверки существования EXISTS, который останавливается на первой найденной строке.
Где спотыкаются
Считают через LEFT JOIN с COUNT(*). Клиент без заказов даёт одну строку с NULL справа и получает 1 вместо 0. Нужно COUNT(o.id).
Проверяют наличие записи через COUNT(*) и сравнение с нулём в Go. На большой таблице это полный подсчёт ради одного бита.
Сканируют COUNT с GROUP BY через QueryRow и не ждут sql.ErrNoRows. Если групп нет, в ответе ноль строк, а не одна строка с нулём.