SQLaggregatesCOUNTSUM

Что такое COUNT, SUM, AVG, MIN, MAX в SQL? Агрегатные функции для начинающих

Агрегатные функции — это инструмент «посчитать что-то по группе строк». COUNT — сколько строк, SUM — сумма, AVG — среднее, MIN/MAX — минимум и максимум. Простыми словами: разница COUNT(*) и COUNT(column), как NULL влияет на агрегаты, разные сценарии и частые ошибки.

4 мин чтенияСправочникSQL · aggregates · COUNT · SUM · tutorial

Это пять самых базовых агрегатных функций. Они отвечают на вопросы вида «сколько», «сколько в сумме», «сколько в среднем», «самое маленькое», «самое большое». Все они «схлопывают» группу строк в одно число.

Если обычный SELECT показывает строки, то агрегатная функция — даёт итог по строкам. Без них любая статистика была бы ручным циклом в коде приложения.

Зачем нужны агрегаты

Любая аналитика начинается с агрегатов:

  • Сколько у нас пользователей? — COUNT(*) FROM users.
  • Сколько денег в этом месяце? — SUM(amount) FROM orders WHERE month = ....
  • Какая средняя оценка товара? — AVG(rating) FROM reviews.
  • Самый дорогой заказ? — MAX(amount) FROM orders.

Они работают и без GROUP BY (тогда вся таблица — одна большая «группа»), и с GROUP BY (одно значение на группу).

COUNT — сколько строк

SELECT COUNT(*) FROM users;
-- → 1247

COUNT(*) — посчитал все строки.

Есть три формы:

  • COUNT(*) — все строки, включая те, где есть NULL.
  • COUNT(column) — строки, где column IS NOT NULL. NULL'ы пропускает.
  • COUNT(DISTINCT column) — уникальные не-NULL значения.

Пример с таблицей users:

id name email
1 Аня anya@example.com
2 Боб NULL
3 Вера vera@example.com
4 Боб bob@example.com
SELECT
  COUNT(*)                AS all_rows,           -- 4
  COUNT(email)            AS with_email,         -- 3 (Боб без email пропускается)
  COUNT(DISTINCT name)    AS unique_names        -- 3 (два Боба считаются одним)
FROM users;

COUNT(*) обычно быстрее всего и используется по умолчанию. COUNT(column) — когда хочешь именно «сколько заполнено». COUNT(DISTINCT) — для уникальных.

SUM — сумма

SELECT SUM(amount) FROM orders;
-- → 156500.00

Складывает все значения колонки. NULL'ы пропускает (не превращает в 0). Если все строки NULL — результат NULL, не 0 (это часто сюрприз).

-- Защита от NULL — обернуть в COALESCE
SELECT COALESCE(SUM(amount), 0) FROM orders WHERE customer_id = 999;

SUM работает с числами (INTEGER, NUMERIC, REAL). На текстовых колонках упадёт.

AVG — среднее

SELECT AVG(rating) FROM reviews;
-- → 4.23

Сумма всех значений / количество не-NULL значений.

Важно: AVG для INTEGER колонки в Postgres возвращает NUMERIC (точно). В некоторых других БД — может вернуть INTEGER с обрезкой:

-- Postgres: AVG(INTEGER) → NUMERIC, точное значение
SELECT AVG(score) FROM tests;
-- → 4.5000000000000000

-- Если хочется округлить до 2 знаков
SELECT ROUND(AVG(score), 2) FROM tests;
-- → 4.50

NULL'ы AVG пропускает, как и SUM.

MIN, MAX — самое маленькое и самое большое

SELECT
  MIN(price) AS cheapest,
  MAX(price) AS most_expensive
FROM products;

Работают с числами, датами, строками (в порядке alphabetical):

-- Самая ранняя и самая поздняя дата заказа
SELECT MIN(created_at), MAX(created_at) FROM orders;

-- Алфавитно первое и последнее имя
SELECT MIN(name), MAX(name) FROM users;

NULL пропускают.

Агрегаты с GROUP BY

Главный сценарий — отчёты по группам:

SELECT
  category,
  COUNT(*)        AS items,
  SUM(price)      AS total,
  AVG(price)      AS avg_price,
  MIN(price)      AS min_price,
  MAX(price)      AS max_price
FROM products
GROUP BY category;

Каждая категория получит свою строку с пятью числами.

Агрегаты с DISTINCT

Все агрегаты принимают DISTINCT:

SELECT
  COUNT(DISTINCT customer_id) AS unique_customers,
  SUM(DISTINCT amount)        AS sum_unique_amounts
FROM orders;

COUNT(DISTINCT customer_id) — самый частый из них, для подсчёта уникальных юзеров. SUM(DISTINCT) редко имеет смысл (обычно нужен SUM, а не сумма уникальных значений).

Поведение с NULL

Функция NULL значения
COUNT(*) Считает все строки, NULL не пропускает
COUNT(column) Пропускает NULL
SUM(column) Пропускает NULL. Если все NULL → возвращает NULL (не 0)
AVG(column) Пропускает NULL. Если все NULL → NULL
MIN/MAX(column) Пропускают NULL. Если все NULL → NULL

Если хочешь, чтобы пустая выборка возвращала 0, а не NULL — оборачивай в COALESCE:

SELECT COALESCE(SUM(amount), 0) FROM orders WHERE customer_id = 999;

Частые ошибки новичков

1. COUNT(*) vs COUNT(column). На таблицах, где половина строк имеют NULL в колонке, разница огромная. COUNT(*) = 100, COUNT(email) = 47 — это нормально и значит «47 из 100 заполнили email».

2. SUM/AVG пустой выборки = NULL, не 0. SELECT SUM(amount) FROM orders WHERE status = 'never' — если ни одной строки, SUM вернёт NULL. В коде приложения тогда упадёт что-то вроде null + 5. Обернул в COALESCE(SUM(amount), 0) — спасся.

3. Агрегат + не-агрегатная колонка без GROUP BY. SELECT name, COUNT(*) FROM users упадёт. Если хочешь «имя и счёт», нужен GROUP BY name. Если хочешь только счёт — без name.

4. AVG на целых числах. В MySQL AVG(INT) возвращает float, теряя точность на больших суммах. В Postgres — NUMERIC, точное. Если есть подозрение на потерю точности — каст: AVG(amount::NUMERIC).

5. MIN/MAX по тексту даёт алфавитный, не числовой порядок. MIN(version) для '1', '10', '2' даст '1', а MAX'2' (а не '10'). Если версии — числа, храни числами или паддуй нулями.

6. COUNT(*) на огромной таблице медленный. Postgres честно бежит по всем строкам (или индексу) — на 100M строк это секунды. Если нужна примерная цифра — есть быстрые приёмы (pg_class.reltuples, статистика планировщика). Точная — только полный COUNT.

Мини-резюме

  • COUNT(*) — все строки. COUNT(column) — не-NULL. COUNT(DISTINCT column) — уникальные.
  • SUM, AVG — складывают / усредняют, NULL пропускают. Пустая выборка → NULL, не 0.
  • MIN, MAX — работают с числами, датами, строками (по алфавиту). NULL пропускают.
  • Хочешь 0 вместо NULL для пустой выборки — COALESCE(SUM(x), 0).
  • В SELECT с агрегатами все остальные колонки должны быть в GROUP BY либо завернуты в агрегат.

Закрепи на практике

Решай задачи в SQL-тренажёре с мгновенной проверкой и подсказками.

Открыть тренажёр