Агрегатные функции

Агрегатная функция сворачивает набор строк в одно значение для всего запроса или каждой группы.

ФункцияСмыслПоведение с NULL
COUNT(*)число строксчитает все строки
COUNT(expr)число известных значенийпропускает NULL
SUM(expr)суммапропускает NULL
AVG(expr)среднеепропускает NULL
MIN, MAXминимум, максимумпропускают NULL
SELECT user_id,
       COUNT(*) AS all_orders,
       COUNT(total) AS orders_with_total,
       COALESCE(SUM(total), 0) AS amount
FROM orders
GROUP BY user_id;

SUM по пустому набору обычно возвращает NULL, а не ноль. Если домен требует ноль, это нужно выразить через COALESCE.

Условная агрегация

Стандартный вариант:

SELECT user_id,
       SUM(CASE WHEN state = 'PAID' THEN 1 ELSE 0 END) AS paid_count,
       SUM(CASE WHEN state = 'NEW'  THEN 1 ELSE 0 END) AS new_count
FROM orders
GROUP BY user_id;

В некоторых СУБД есть синтаксис FILTER, но CASE переносимее.

DISTINCT внутри агрегата

SELECT COUNT(DISTINCT user_id)
FROM orders
WHERE state = 'PAID';

Это количество пользователей, а не заказов. DISTINCT может потребовать сортировки или хеш-таблицы, поэтому его стоимость стоит смотреть в плане.

Точность

Для денег используют точный десятичный тип NUMERIC/DECIMAL, а не двоичный FLOAT. Даже при точном типе нужно заранее определить правила округления и распределения остатка.

Ловушки

  • После LEFT JOIN выражение COUNT(*) минимум равно одной строке для левой записи; для числа дочерних объектов считай COUNT(child.id).
  • AVG(AVG(...)) обычно неверно для групп разного размера; нужны сумма и количество.
  • Агрегация после двух независимых связей «один ко многим» может дать декартово размножение.

Связано: Оператор GROUP BY, Оператор HAVING, Исключение дубликатов, DISTINCT.

Вопросы

Чем COUNT(*) отличается от COUNT(column)? Первый считает строки, второй — ненулевые значения.

Что вернет SUM по пустому набору? Обычно NULL; ноль задают явно через COALESCE.

Почему нельзя усреднять средние? Группы могут иметь разный вес.

Как считать дочерние записи после LEFT JOIN? COUNT(child.id), а не COUNT(*).