Урок курса

Агрегатные функции: COUNT, SUM, AVG, MIN, MAX

SQL с нуля: бесплатный курс с практикой

В прошлом уроке функции вроде ROUND или LENGTH принимали одно значение и возвращали одно значение — каждая строка обрабатывалась независимо. Агрегатные функции работают иначе: они принимают сразу весь набор строк и сворачивают его в одно итоговое число.

Агрегатная функция: набор строк → одно итоговое значение

Представьте, что вам нужно узнать не содержимое конкретной строки, а что-то про всю таблицу целиком: сколько в ней заказов, какова их общая сумма, каков средний чек. Обычный SELECT здесь не поможет — он возвращает по одной строке на каждую запись. Агрегатная функция делает другое: она получает на вход множество строк и возвращает одно число.

Когда в SELECT стоят только агрегатные функции и нет GROUP BY, результат — всегда ровно одна строка. Не одна строка на каждый заказ, не пустой результат — именно одна строка с итоговыми значениями по всей таблице.

Пять основных функций выглядят так:

SELECT
    COUNT(*)          AS количество,
    SUM(amount)       AS итого,
    AVG(amount)       AS среднее,
    MIN(amount)       AS минимум,
    MAX(amount)       AS максимум
FROM orders;

Этот запрос вернёт одну строку с пятью столбцами. COUNT(*) — сколько строк в таблице. SUM(amount) — арифметическая сумма всех значений столбца amount. AVG(amount) — среднее арифметическое. MIN и MAX — наименьшее и наибольшее значение соответственно.

Почему именно одна строка? Потому что агрегатные функции в этом запросе описывают весь набор как единое целое. Пока мы считаем итог по всей таблице, в SELECT оставляйте только агрегатные выражения: тогда результатом будет одна строка. Как получить отдельный итог для каждой категории или клиента, разберём в следующем уроке.

Пять агрегатных функций на трёх строках

Запустите запрос целиком в SQLite или PostgreSQL. Временный набор orders содержит суммы 100, 300 и неизвестную сумму NULL:

WITH orders(amount) AS (
    VALUES (100), (300), (NULL)
)
SELECT COUNT(*) AS rows_total,
       COUNT(amount) AS amounts_known,
       SUM(amount) AS total,
       AVG(amount) AS average,
       MIN(amount) AS smallest,
       MAX(amount) AS largest
FROM orders;

Получится одна строка: 3 | 2 | 400 | 200 | 100 | 300. COUNT(*) увидел все три строки. Остальные функции использовали два известных значения: среднее равно 400 / 2, а не 400 / 3. Это различие пригодится при расчёте среднего чека по неполной выгрузке.

COUNT(*) vs COUNT(столбец) и поведение NULL в агрегатных функциях

Разберём тонкость, которая часто даёт неожиданные результаты. Пусть таблица orders содержит пять строк, но в двух из них amount равен NULL — например, заказы зарегистрированы, но сумма ещё не проставлена.

SELECT
    COUNT(*)       AS все_строки,
    COUNT(amount)  AS строки_с_суммой,
    SUM(amount)    AS итого
FROM orders;

Результат:

все_строки строки_с_суммой итого
5 3 …

COUNT(*) считает строки — все, независимо от того, что в них записано. COUNT(amount) считает только те строки, где amount не равен NULL. Поэтому первое число — 5, второе — 3.

SUM, AVG, MIN, MAX ведут себя как COUNT(столбец): они просто игнорируют строки с NULL и работают только с теми значениями, которые есть. SUM(amount) сложит три ненулевых значения и вернёт их сумму. AVG поделит ту же сумму на 3, а не на 5 — это важно: среднее считается по фактически присутствующим значениям, а не по общему количеству строк.

Есть крайний случай: если NULL стоит в столбце у всех строк, SUM, AVG, MIN, MAX вернут NULL. Это логично — нечего агрегировать. А вот COUNT(*) в той же ситуации вернёт не NULL, а количество строк — он вообще никогда не возвращает NULL, даже для полностью пустой таблицы вернёт 0.

Практическое следствие: если вы хотите знать, сколько заказов у вас в системе — используйте COUNT(*). Если хотите знать, для скольких из них уже проставлена сумма — используйте COUNT(amount). Это разные вопросы, и разница проявляется именно там, где есть NULL.

Агрегация после WHERE: функция видит только отфильтрованные строки

Когда в запросе есть WHERE, агрегатная функция получает на вход не всю таблицу, а только строки, прошедшие фильтр. Порядок выполнения жёсткий: сначала FROM определяет таблицу, затем WHERE отбирает строки, и лишь потом агрегатная функция работает с тем, что осталось.

SELECT
    COUNT(*)    AS количество,
    SUM(amount) AS итого
FROM orders
WHERE amount >= 1000;

Этот запрос считает и суммирует только те заказы, у которых amount не меньше 1000. Заказы с меньшей суммой до агрегатной функции не доходят вообще — они отсеяны раньше.

Важно понять, что WHERE и агрегация — это не конкуренты, а последовательные этапы. WHERE не знает об агрегатных функциях, а агрегатная функция не знает о строках, которые WHERE уже отбросил. Именно поэтому нельзя написать WHERE COUNT(*) > 10 — в момент проверки WHERE агрегация ещё не произошла, значения COUNT(*) просто не существует. Для фильтрации по результату агрегации есть отдельный инструмент, который появится в следующих уроках.

Практически это означает: комбинируя WHERE с агрегатными функциями, вы задаёте вопрос «какова итоговая картина по конкретному подмножеству данных?». Например, средний чек среди заказов дороже 1000, количество заказов за конкретный период или максимальная сумма среди заказов дешевле 5000 — всё это пишется именно так: условие в WHERE, итог в SELECT.

Задача 1

Таблица orders содержит пять строк. Сколько строк вернёт запрос без GROUP BY?

SELECT COUNT(*), SUM(amount), MIN(amount), MAX(amount) FROM orders;

Выберите один вариант
Без регистрации · результат не сохраняется

Задача 2

Что вернёт SELECT SUM(price) FROM products WHERE price > 100000, если ни одна строка не удовлетворяет условию?

Выберите один вариант
Без регистрации · результат не сохраняется

Задача 3

Таблица содержит 4 строки со значениями столбца score: 10, NULL, 30, NULL. Чему равны COUNT(), COUNT(score) и AVG(score)? Ответьте в формате: COUNT() = ?, COUNT(score) = ?, AVG(score) = ?

Без регистрации · результат не сохраняется

Задача 4

Сводка по оплаченным заказам

Постройте одну строку сводки только по заказам со status = 'paid'. По порядку верните: число всех отобранных строк как paid_rows, число известных значений amount как known_amounts, сумму как total_amount, среднее с округлением до двух знаков как avg_amount, минимум как min_amount и максимум как max_amount.

РешениеSQL · SQLite

Без регистрации. Гостевая проверка не сохраняет прогресс. Для сохранения перейдите к уроку.

Отправьте решение, чтобы увидеть результаты тестов.

Задачи к уроку

Все задачи курса →

Продолжить с сохранением прогресса

Перейдите к уроку в учебном кабинете.

Перейти к интерактивному уроку