В отчёте после добавления группировки число строк резко уменьшилось. Какой механизм определяет новое количество строк результата?
GROUP BY разбивает исходные строки на группы по уникальным комбинациям указанных ключей, а затем возвращает обычно одну строку на каждую такую группу. Поэтому количество строк результата определяется не числом исходных строк, а числом различных групп.
Если в группировке участвуют несколько столбцов, группа формируется для каждой уникальной комбинации их значений. Агрегатная функция вычисляется по строкам внутри своей группы.
Агрегация появилась как средство получать сводные данные из набора детальных записей: итоги продаж, количество событий, минимальные и максимальные значения. Исходная проблема состояла в том, что аналитический результат требовалось представить на более высоком уровне детализации, чем исходные данные.
GROUP BY решает эту задачу декларативно: разработчик указывает измерения группировки и агрегаты, а СУБД сама формирует группы и вычисляет итоговые значения.
Допустим, таблица содержит одну строку на заказ, но отчёт должен содержать одну строку на магазин. После группировки данные перестают быть отчётом об отдельных заказах: они становятся отчётом о магазинах.
Неверная оценка этого эффекта приводит к ошибкам в интерфейсах, пагинации и последующих расчётах. Например, приложение может ожидать тысячи строк заказов, а получить десятки строк магазинов и ошибочно посчитать это потерей данных.
Сначала СУБД определяет значение ключа группировки для каждой исходной строки. Все строки с одинаковой комбинацией значений ключей попадают в одну группу. Затем для каждой группы вычисляются агрегаты, такие как COUNT, SUM, AVG, MIN и MAX.
Число строк результата равно числу сформированных групп, если запрос использует GROUP BY и не применяет последующее объединение или фильтрацию. Например, при группировке по магазину будет не более одной строки на магазин, представленный в исходном наборе.
В этом запросе десять тысяч заказов могут превратиться в пятьдесят строк, если заказы принадлежат пятидесяти магазинам. COUNT(*) считается внутри каждой группы, а не по всей таблице.
Группировка не сохраняет произвольную детализацию исходных строк. Столбец можно вывести без агрегирования только тогда, когда он входит в ключ группировки либо СУБД допускает его вывод на основании гарантированной функциональной зависимости. В переносимом SQL безопасно считать, что каждый выбранный неагрегированный столбец должен быть частью группировки.
Значения NULL в ключе группировки не образуют отдельную группу для каждой строки: строки с NULL в соответствующем ключе группируются вместе. При нескольких ключах сравнивается вся комбинация значений.
Пустой входной набор требует отдельного внимания. Для агрегатного запроса без GROUP BY обычно формируется одна итоговая строка, например с нулевым COUNT(*); при наличии GROUP BY групп не возникает, поэтому результат не содержит строк. Точные детали отдельных агрегатов зависят от их семантики: например, SUM по отсутствующим значениям обычно возвращает NULL, а не ноль.
Команда строила отчёт по магазинам на основе таблицы заказов. В исходном варианте сервис загружал все заказы, группировал их в памяти и затем возвращал итоговое число заказов по каждому магазину.
Рассматривались три варианта:
Выбрали третий вариант. Он соответствовал требуемому уровню детализации, уменьшил объём результата и позволил оптимизатору использовать индексы, статистику и план выполнения базы данных. При этом детальные заказы оставили в отдельном запросе, поскольку сводный отчёт намеренно не должен их сохранять.
Что произойдёт при группировке по двум столбцам вместо одного?
Группа будет определяться уникальной парой значений, а не каждым столбцом независимо. Например, магазин и месяц образуют отдельную группу для каждого сочетания магазина с месяцем. Поэтому добавление ключа группировки обычно увеличивает или сохраняет число групп, но не может уменьшить его по сравнению с группировкой по подмножеству ключей.
Чем отличается отсутствие группировки от группировки по константному выражению?
В запросе без GROUP BY агрегаты вычисляются по всему входному набору, то есть получается общий итог. Группировка по одному и тому же константному значению создаёт одну группу только при наличии строк во входе, а на пустом входе группы не будет. Это различие важно для поведения результата на пустых данных.
Почему добавление агрегатной функции само по себе не всегда означает одну строку на всю таблицу?
Если в запросе есть GROUP BY, агрегат вычисляется отдельно для каждой группы, поэтому строк будет столько, сколько групп. Одна общая строка получается только при агрегировании без группировки либо после дополнительного шага, который снова сводит группы в один результат. Наличие SUM или COUNT не определяет гранулярность без учёта структуры всего запроса.