Menu

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

Как агрегатные функции в SQLite сворачивают множество строк в одно значение — COUNT, SUM, AVG, MIN, MAX, GROUP_CONCAT, плюс DISTINCT, FILTER и правила работы с NULL.

На этой странице есть исполняемые редакторы: меняйте, запускайте и сразу видите результат.

Что на самом деле делает агрегат

Большинство SQL-функций, с которыми вы уже сталкивались, работают построчно: UPPER(name) отрабатывает один раз на строку, ROUND(price, 2) — то же самое. Агрегатные функции устроены иначе. Они смотрят на набор строк целиком и сворачивают его в одно значение.

Заведём небольшую таблицу для опытов:

На входе пять строк, на выходе — одна. Вот и вся ментальная модель: агрегаты сжимают строки в сводку. Без GROUP BY сводка покрывает все строки результата.

COUNT: строки против значений

У COUNT есть три формы, и разница между ними заметна:

  • COUNT(*) считает строки. NULL-ы тоже. Всегда возвращает число.
  • COUNT(column) считает не-NULL значения в этом столбце.
  • COUNT(DISTINCT column) считает уникальные не-NULL значения.

Пять строк, у трёх есть amount, трое разных клиентов. Если COUNT(amount) оказался меньше COUNT(*) — вот почему: NULL-ы не считаются.

SUM, AVG, MIN, MAX

Арифметические агрегаты ведут себя ожидаемо, с одним негромким правилом — все они пропускают NULL:

AVG тут — это (10 + 20 + 30) / 3 = 20.0, а не 60 / 4 = 15.0. Знаменатель — количество не-NULL значений. Если хочется, чтобы пропуски считались как ноль, скажите об этом явно:

MIN и MAX работают и со строками, и с датами: для текста — лексикографическое сравнение, для дат в стандартном ISO-формате — как с обычными ISO-строками.

SUM против TOTAL

В SQLite есть второй «суммирующий» агрегат — TOTAL, — который снимает пару раздражающих особенностей SUM:

  • SUM от нуля строк возвращает NULL. TOTAL0.0.
  • SUM от сплошных NULL-ов возвращает NULL. TOTAL0.0.
  • TOTAL всегда возвращает число с плавающей точкой и не страдает целочисленным переполнением.

Платой идёт нестандартность: TOTAL — это расширение SQLite, и результат всегда REAL, что может неожиданно прилететь, если вы рассчитывали на целое. Берите его, когда «нет строк — значит ноль» — это правильный ответ для вашего приложения, и оставайтесь с SUM, когда важно стандартное поведение.

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

DISTINCT можно поставить внутрь любого агрегата, не только COUNT. Он убирает дубликаты значений до агрегации:

SUM(amount) складывает суммы всех строк. SUM(DISTINCT amount) берёт каждую уникальную сумму один раз — может быть полезно для чего-то вроде «общая сумма уникальных счетов», но в обычной работе нужно редко. По-настоящему распространённый вариант — COUNT(DISTINCT customer).

FILTER: агрегация по подмножеству

Когда нужно агрегировать только часть строк, первое, что приходит в голову, — WHERE. Но WHERE фильтрует всё подряд: «посчитать оплаченные заказы» и «посчитать возвраты» в одном запросе так не сделать. Эту задачу решает FILTER:

Каждое FILTER (WHERE ...) действует только на свой агрегат. Один проход по таблице — несколько срезов сразу. До появления FILTER ту же задачу решали через SUM(CASE WHEN status = 'paid' THEN amount END) — идея та же, но многословнее.

GROUP_CONCAT: склеить значения в строку

GROUP_CONCAT — белая ворона среди агрегатов. Вместо числа он возвращает значения, склеенные в одну строку:

Разделитель по умолчанию — запятая. Передайте второй аргумент, чтобы поставить что-то другое. Порядок не гарантирован, если только вы не пишете GROUP_CONCAT(tag ORDER BY tag) — пригодится, когда вывод появляется в интерфейсе и должен быть стабильным.

Агрегация без GROUP BY

Каждый пример выше с агрегатами без GROUP BY возвращал ровно одну строку. Это и есть правило: SELECT с агрегатами и без GROUP BY — это сводка по всей таблице (после WHERE), укладывающаяся в одну строку.

Агрегаты в одном запросе можно смешивать:

А вот чего делать не стоит — это смешивать неагрегированные столбцы с агрегатами и ждать осмысленного результата:

-- SQLite разрешит, но значение `customer` будет произвольным.
SELECT customer, SUM(amount) FROM orders;

SQLite ошибки тут не выдаст (другие СУБД — выдадут), но просто наугад покажет имя одного из клиентов рядом с общей суммой. Если нужна сумма по каждому клиенту — нужен GROUP BY, и про него — следующая страница.

Дальше: GROUP BY и HAVING

Агрегаты по всей таблице отвечают на «сколько в сумме». Агрегаты по группам — по клиенту, по месяцу, по статусу — отвечают на куда более интересные вопросы. GROUP BY — это про то, как разложить строки по корзинам перед агрегацией, а HAVING — как отфильтровать сами группы по результату агрегата. Об этом — дальше.

Часто задаваемые вопросы

Что такое агрегатные функции в SQLite?

Агрегатные функции принимают много строк и возвращают одно итоговое значение. Встроенные — это COUNT, SUM, AVG, MIN, MAX, TOTAL и GROUP_CONCAT. Без GROUP BY они сворачивают весь результирующий набор в одну строку.

В чём разница между SUM и TOTAL в SQLite?

Обе складывают числа, но SUM возвращает NULL, когда все входные значения — NULL, и по возможности использует целочисленную арифметику (которая может переполниться). TOTAL всегда возвращает число с плавающей точкой и 0.0, если строк нет вовсе. Берите TOTAL, когда нужен гарантированно числовой результат, и SUM, когда важно поведение по стандарту SQL.

Как посчитать количество уникальных значений в SQLite?

Поставьте DISTINCT внутрь вызова: COUNT(DISTINCT customer_id). Это посчитает уникальные значения, не считая NULL. Просто COUNT(column) считает не-NULL значения, включая дубликаты, а COUNT(*) — все строки целиком, независимо от NULL.

Игнорируют ли агрегаты SQLite значения NULL?

Да — каждый агрегат, кроме COUNT(*), пропускает NULL на входе. AVG делит на количество не-NULL значений, а не на общее число строк. Исключение — COUNT(*): он считает строки, а не значения, поэтому NULL-ы тоже попадают в счёт.

Coddy programming languages illustration

Учитесь программировать с Coddy

НАЧАТЬ