Чтобы посчитать количество строк в SQL, используйте функцию COUNT: SELECT COUNT(*) FROM orders; вернёт одно число, сколько строк в таблице. Добавьте WHERE, и база посчитает только подходящие строки, добавьте GROUP BY, и получите количество по каждой группе.
У функции три формы, и путают их чаще всего. COUNT(*) считает строки целиком, COUNT(столбец) считает только непустые значения, COUNT(DISTINCT столбец) считает уникальные непустые. С чего начинать сам язык, мы рассказали в обзоре языка SQL, а WHERE и сортировку разбирали в статье о команде SELECT в MySQL.
Вывод в примерах взят из MySQL 8.0 и PostgreSQL 16. Где базы отвечают по-разному, показаны оба варианта.
Синтаксис COUNT и учебные таблицы

COUNT относится к агрегатным функциям: она получает много строк, а возвращает одно значение. Общий вид запроса такой:
SELECT COUNT(*) -- все строки
SELECT COUNT(столбец) -- строки, где в столбце не NULL
SELECT COUNT(DISTINCT столбец) -- разные значения без NULL
FROM таблица
WHERE условие;
Дальше работаем с двумя маленькими таблицами интернет-магазина. В orders шесть заказов, у трёх есть промокод:
CREATE TABLE customers (id INT PRIMARY KEY, name VARCHAR(20), city VARCHAR(20));
INSERT INTO customers VALUES
(1, 'Анна', 'Москва'), (2, 'Борис', 'Казань'), (3, 'Вера', 'Москва'),
(4, 'Глеб', NULL), (5, 'Дина', 'Казань');
CREATE TABLE orders (id INT PRIMARY KEY, customer_id INT, amount INT,
status VARCHAR(10), promo_code VARCHAR(10));
INSERT INTO orders VALUES
(1, 1, 1200, 'paid', 'AUTUMN'), (2, 1, 800, 'paid', NULL),
(3, 2, 3500, 'cancelled', NULL), (4, 3, 450, 'paid', 'AUTUMN'),
(5, 3, 450, 'new', NULL), (6, 5, 2100, 'paid', 'FRIEND');
Обратите внимание на Глеба: у него нет ни одного заказа и не указан город. Он понадобится, когда дойдём до ошибок.
COUNT(*), COUNT(столбец) и COUNT(DISTINCT): в чём разница
Запустим все три формы по столбцу с промокодом одним запросом:
SELECT COUNT(*), COUNT(promo_code), COUNT(DISTINCT promo_code)
FROM orders;
+----------+-------------------+----------------------------+
| COUNT(*) | COUNT(promo_code) | COUNT(DISTINCT promo_code) |
+----------+-------------------+----------------------------+
| 6 | 3 | 2 |
+----------+-------------------+----------------------------+
- 6: всего строк в таблице, пустые промокоды не мешают.
- 3: заказов с промокодом. Три строки с NULL функция пропустила.
- 2: разных промокодов, AUTUMN и FRIEND. Повтор AUTUMN посчитан один раз.
Так и описано в документации: справка PostgreSQL говорит, что count(*) выдаёт количество входных строк, а count(значение) только тех, где значение отлично от NULL. Подробнее о пустых значениях и о том, почему NULL не равен пустой строке, мы писали в статье NULL в SQL.
COUNT(1) и COUNT(*)
Результат у них одинаковый. Внутри скобок стоит константа, она никогда не бывает NULL, поэтому COUNT(1) тоже считает каждую строку:
SELECT COUNT(*), COUNT(1), COUNT(id) FROM orders;
-- 6 | 6 | 6
Про скорость в документации MySQL сказано прямо: InnoDB обрабатывает COUNT(*) и COUNT(1) одинаково, разницы в производительности нет. Сайт документации MySQL из России открывается не всегда. Пишите COUNT(*), так запрос читается однозначно.
COUNT(DISTINCT) и SELECT DISTINCT дают разное число
Сравните два способа посчитать города покупателей:
SELECT COUNT(DISTINCT city) FROM customers; -- 2
SELECT COUNT(*) FROM (SELECT DISTINCT city FROM customers) t; -- 3
Первый запрос выбросил NULL. Второй сначала вывел уникальные значения, а в этом списке NULL остаётся отдельной строкой, и COUNT(*) её посчитал. Если «город не указан» тоже должен попасть в итог, нужен второй вариант. Как работает сам оператор, разбирали в статье про DISTINCT в SQL.
Канал основателя Checkroi Вани Буявца3 700 человек читают мой Телеграм-канал про нейросетиСобрал промпты для Claude Code и ChatGPT, разборы ИИ-инструментов и лайфхаки по продвижению бизнеса в одном местеПрисоединитьсяКак посчитать строки с условием
Одно условие: WHERE
Сколько заказов оплачено:
SELECT COUNT(*) FROM orders WHERE status = 'paid';
-- 4
WHERE отбирает строки до подсчёта, поэтому COUNT видит только четыре оплаченных заказа. Для нескольких условий работают AND и OR, как в обычном SELECT. Набить руку на таких фильтрах проще на задачах с проверкой, программы с ними собраны в разделе курсов по SQL.
Несколько счётчиков в одном запросе
Когда нужно сразу «всего», «оплачено» и «с промокодом», три запроса с разными WHERE не понадобятся. COUNT пропускает NULL, и на этом строится условный подсчёт: CASE возвращает 1 для подходящей строки и NULL для остальных.
SELECT COUNT(*) AS total,
COUNT(CASE WHEN status = 'paid' THEN 1 END) AS paid,
COUNT(CASE WHEN promo_code IS NOT NULL THEN 1 END) AS with_promo
FROM orders;
+-------+------+------------+
| total | paid | with_promo |
+-------+------+------------+
| 6 | 4 | 3 |
+-------+------+------------+
CASE входит в стандарт SQL, поэтому такой запрос переносится между базами без правок. Короткие записи тоже есть, но у каждой своя база:
| Запись | Где работает | Результат на наших данных |
|---|---|---|
COUNT(CASE WHEN status = 'paid' THEN 1 END) |
MySQL, PostgreSQL, SQLite и другие базы со стандартным CASE | 4 |
SUM(status = 'paid') |
MySQL: сравнение там возвращает 1 или 0 | 4 |
COUNT(*) FILTER (WHERE status = 'paid') |
PostgreSQL, SQLite | 4 |
Отсюда же считается доля: COUNT(CASE WHEN status = 'cancelled' THEN 1 END) * 100.0 / COUNT(*) даст процент отменённых заказов одной строкой. На наших данных это около 16,7 %: один заказ из шести.
Подсчёт по группам: GROUP BY и HAVING

GROUP BY делит строки на группы, и COUNT считает каждую отдельно. Сколько заказов в каждом статусе:
SELECT status, COUNT(*) AS orders_count
FROM orders
GROUP BY status
ORDER BY orders_count DESC;
+-----------+--------------+
| status | orders_count |
+-----------+--------------+
| paid | 4 |
| cancelled | 1 |
| new | 1 |
+-----------+--------------+
Чтобы оставить только группы с нужным количеством, условие пишут в HAVING, он проверяется уже после подсчёта. Покупатели, у которых больше одного заказа:
SELECT customer_id, COUNT(*) AS orders_count
FROM orders
GROUP BY customer_id
HAVING COUNT(*) > 1;
+-------------+--------------+
| customer_id | orders_count |
+-------------+--------------+
| 1 | 2 |
| 3 | 2 |
+-------------+--------------+
Ровно эта задача, найти клиентов с повторными заказами, разобрана среди задач в статье о собеседовании аналитика данных.
Группировка с подсчётом закрывает большую часть рабочих отчётов: сколько заявок по менеджерам, клиентов по городам. Из таких запросов состоит повседневная работа SQL-аналитика.
Общее количество строк рядом с LIMIT
Типичная задача для страницы с пагинацией: показать первые записи и тут же написать «найдено 4». Оконная функция COUNT(*) OVER () возвращает общее число в каждой строке, при этом сами строки не схлопываются:
SELECT id, amount, COUNT(*) OVER () AS total
FROM orders
WHERE status = 'paid'
ORDER BY amount DESC
LIMIT 2;
+----+--------+-------+
| id | amount | total |
+----+--------+-------+
| 6 | 2100 | 4 |
| 1 | 1200 | 4 |
+----+--------+-------+
Окно считается до LIMIT, поэтому в total стоит 4, хотя строк выведено две. Работает в PostgreSQL и в MySQL начиная с восьмой версии.
Со скобками OVER (PARTITION BY customer_id) функция посчитает заказы каждого покупателя и поставит число рядом с каждым его заказом:
SELECT id, customer_id,
COUNT(*) OVER (PARTITION BY customer_id) AS customer_orders
FROM orders;
-- у заказов 1 и 2 будет 2, у заказа 3 будет 1
В старых проектах на MySQL для той же задачи встречается связка SQL_CALC_FOUND_ROWS и FOUND_ROWS(). В руководстве MySQL она помечена устаревшей с версии 8.0.17, и её обещают убрать. Взамен там советуют второй запрос с COUNT(*) без LIMIT.
Сколько строк в большой таблице: точно и примерно
На большой таблице SELECT COUNT(*) FROM orders работает заметно дольше, чем выборка десяти строк. Причина описана в разделе про агрегатные функции MySQL: InnoDB не хранит готовое число строк, потому что параллельные транзакции видят разное их количество, поэтому COUNT каждый раз проходит по индексу.
Когда хватит порядка величины, берут оценку из служебных таблиц:
| СУБД | Где взять примерное число | Насколько точно |
|---|---|---|
| MySQL | SELECT TABLE_ROWS FROM information_schema.TABLES WHERE TABLE_NAME = 'orders'; |
для InnoDB это грубая оценка для оптимизатора |
| PostgreSQL | SELECT reltuples FROM pg_class WHERE relname = 'orders'; |
примерная оценка для планировщика, обновляется VACUUM и ANALYZE |
Для отчёта, где число должно сойтись до единицы, подходит только COUNT. Для подписи «около 2 млн записей» в админке хватит оценки.
Ещё одна деталь касается типа результата. MySQL и PostgreSQL возвращают COUNT как BIGINT, так записано в справке MySQL и в справке PostgreSQL. В SQL Server функция COUNT возвращает int, а если строк может оказаться больше предела int, есть COUNT_BIG с типом bigint. Запросы к PostgreSQL удобно запускать из DBeaver, как его настроить, рассказали в инструкции по подключению DBeaver к PostgreSQL.
Частые ошибки с COUNT и что с ними делать

COUNT(*) после LEFT JOIN
Задача: сколько заказов у каждого покупателя, включая тех, у кого их нет. Берём LEFT JOIN, чтобы Глеб не пропал из списка:
SELECT c.name, COUNT(*) AS wrong, COUNT(o.id) AS correct
FROM customers c
LEFT JOIN orders o ON o.customer_id = c.id
GROUP BY c.id, c.name;
+------------+-------+---------+
| name | wrong | correct |
+------------+-------+---------+
| Анна | 2 | 2 |
| Борис | 1 | 1 |
| Вера | 2 | 2 |
| Глеб | 1 | 0 |
| Дина | 1 | 1 |
+------------+-------+---------+
У Глеба COUNT(*) показал один заказ. LEFT JOIN оставил для него строку, где все поля заказа NULL, а COUNT(*) считает строки, а не заказы.
Решение: считайте столбец из присоединённой таблицы, лучше ключ, COUNT(o.id). Он пропустит NULL и даст честный ноль.
Условие на количество в WHERE
Запрос ... WHERE COUNT(*) > 1 GROUP BY customer_id не выполнится ни в MySQL, ни в PostgreSQL, ни в SQLite. Тексты ошибок такие:
MySQL: ERROR 1111 (HY000): Invalid use of group function
PostgreSQL: ERROR: aggregate functions are not allowed in WHERE
SQLite: misuse of aggregate: COUNT()
WHERE работает с отдельными строками до группировки, а количество появляется только после неё. Решение: перенесите условие в HAVING, как в примере выше.
COUNT по столбцу, где бывают пустые значения
Запрос SELECT COUNT(city) FROM customers; вернёт 4, хотя покупателей пять: у Глеба город не указан. Если нужно число людей, считайте COUNT(*) или ключ таблицы. COUNT(столбец) оставьте для вопроса «у скольких заполнено поле».
Ожидание NULL на пустой выборке
Если условию не подошла ни одна строка, COUNT вернёт 0, а SUM вернёт NULL:
SELECT COUNT(*), SUM(amount) FROM orders WHERE amount > 100000;
-- COUNT(*) = 0, SUM(amount) = NULL
В справке PostgreSQL сказано, что все агрегатные функции, кроме count, возвращают NULL, если не выбрано ни одной строки.
С GROUP BY картина другая: на пустой выборке запрос вернёт не строку с нулём, а пустой результат без строк. Если в отчёте должны стоять все статусы, даже с нулём, берите справочник статусов и присоединяйте к нему заказы через LEFT JOIN с COUNT(o.id).
Медленные подсчёты на живой базе разбирают по плану выполнения, этим в команде обычно занят тот, о ком мы писали в статье кто такой администратор баз данных.
Где освоить SQL системно
COUNT разбирается за вечер. Уверенно писать отчёты с группировками, оконными функциями и подзапросами учатся неделями на реальных данных, и курс с задачами и проверкой ускоряет этот путь.
Ниже программы из нашего каталога, все варианты собраны в разделе курсов по SQL. План обучения по месяцам расписан в статье как стать SQL-разработчиком.
| Курс | Школа | Стоимость со скидкой | В рассрочку | Длительность | Обзор курса от Checkroi |
|---|---|---|---|---|---|
| Нейросети для рабочих задач Перейти на сайт курса | 31 290 ₽ | - | 1 месяц | Обзор курса | |
| SQL-разработчик: тариф PRO Перейти на сайт курса | 89 900 ₽ | 7491 ₽/мес. | 2 месяца | Обзор курса | |
| SQL-разработчик: тариф Базовый Перейти на сайт курса | 79 900 ₽ | 3329 ₽/мес. | 3 месяца | Обзор курса | |
| SQL для анализа данных Перейти на сайт курса | 50 828 ₽ | 6203 ₽/мес. | 2 месяца | Обзор курса | |
| SQL с нуля | 134 640 ₽ | 5500 ₽/мес. | 12 месяцев | Обзор курса |
Больше программ — в полном каталоге курсов по SQL




