Как посчитать количество строк, удовлетворяющих условию, внутри одного запроса (COUNT с условием)?
Хочу в одном запросе получить и общее число заказов, и сколько из них оплачено. Делать два отдельных COUNT с разными WHERE неудобно. Как посчитать строки по условию в той же выборке, без второго запроса?
2 ответа
Трюк в том, что COUNT считает только не-NULL значения. Если внутри него поставить выражение, которое для нужных строк даёт значение, а для остальных — NULL, получится условный подсчёт.
Универсальный способ — COUNT(CASE ...):
SELECT
COUNT(*) AS total,
COUNT(CASE WHEN status = 'paid' THEN 1 END) AS paid,
COUNT(CASE WHEN status = 'cancelled' THEN 1 END) AS cancelled
FROM orders;
Здесь CASE WHEN status = 'paid' THEN 1 END возвращает 1 для оплаченных и NULL для остальных (ветки ELSE нет — значит NULL), и COUNT их посчитает. Один проход по таблице, несколько счётчиков сразу.
Ещё чаще для «сколько строк подходит» удобнее SUM с условием, потому что условие даёт 1/0:
SELECT
SUM(CASE WHEN total > 1000 THEN 1 ELSE 0 END) AS big_orders
FROM orders;
В PostgreSQL есть ещё более лаконичный синтаксис FILTER:
SELECT
COUNT(*) AS total,
COUNT(*) FILTER (WHERE status = 'paid') AS paid
FROM orders;
Частая ошибка — писать COUNT(status = 'paid'): булево выражение само по себе не NULL, и COUNT посчитает все строки, и true, и false. Нужна именно конструкция через CASE (или FILTER), чтобы ненужные строки превращались в NULL.
Это особенно мощно в связке с GROUP BY — так строят сводные отчёты «по строкам категория, по столбцам статусы»:
SELECT
category,
COUNT(*) AS total,
COUNT(CASE WHEN status = 'paid' THEN 1 END) AS paid
FROM orders
GROUP BY category;
Получаете по каждой категории и общее число, и число оплаченных рядом. Такой приём называют условной агрегацией или поворотом (pivot) данных.