Learning Platform
Глоссарий Troubleshooting
Урок 04.03 · 19 мин
Средний
friendly-sqlgroup-byorder-by

GROUP BY ALL и ORDER BY ALL

В любом агрегирующем запросе есть скрытое правило, которое SQL заставляет соблюдать вручную: каждая неагрегатная колонка из SELECT обязана присутствовать в GROUP BY. Если в SELECT стоит region, product, sum(amount), то GROUP BY обязан содержать ровно region, product. Это правило — следствие семантики группировки: движок не знает, какое значение region показать для группы, если region не входит в ключ группировки.

Проблема в том, что список группировки — это дубликат неагрегатной части SELECT. Два списка, которые должны совпадать всегда, но синхронизировать их обязаны вы. Добавили колонку в SELECT — не забудьте добавить в GROUP BY. На широком запросе с шестью группировочными колонками это превращается в источник тупых, но раздражающих ошибок. GROUP BY ALL устраняет дубликат: DuckDB сам выводит ключи группировки из SELECT. ORDER BY ALL решает родственную, хоть и более редкую задачу для сортировки.


Дубликат, который приходится синхронизировать

Посмотрим на обычный агрегирующий запрос и на то, какая информация в нём повторяется:

SELECT
    region,
    product,
    extract(year FROM order_date) AS yr,
    sum(amount) AS revenue,
    count(*) AS orders
FROM sales
GROUP BY region, product, extract(year FROM order_date);

Список GROUP BY — это в точности неагрегатные элементы SELECT: region, product и выражение extract(year FROM order_date). Агрегаты sum(amount) и count(*) в GROUP BY не входят. Вы написали одну и ту же информацию дважды, и теперь обязаны держать копии синхронными.

GROUP BY ALL выводит ключи из SELECT
SELECT: region, product, yr, sum(amount), count(*)Список SELECT смешивает неагрегатные колонки и агрегаты. Неагрегатные — это и есть ключи группировки.
GROUP BY ALL анализирует
agg: sum(amount), count(*) — пропуститьDuckDB определяет, какие элементы SELECT являются вызовами агрегатных функций, и исключает их из ключей группировки.
ключи: region, product, yrВсё, что не агрегат, становится ключом GROUP BY. Список выводится автоматически, дублировать SELECT не нужно.

GROUP BY ALL заменяет ручной список ключевым словом ALL. DuckDB смотрит на SELECT, отделяет агрегатные выражения от неагрегатных и берёт все неагрегатные как ключи группировки:

-- Тот же запрос, GROUP BY выведен автоматически
SELECT
    region,
    product,
    extract(year FROM order_date) AS yr,
    sum(amount) AS revenue,
    count(*) AS orders
FROM sales
GROUP BY ALL;

Результат идентичен предыдущему запросу. GROUP BY ALL развернулся в GROUP BY region, product, extract(year FROM order_date) ещё на этапе биндинга. Оптимизатор и физический план не отличаются никак — это чисто синтаксическое удобство, разворачиваемое до планирования.


Как DuckDB отличает агрегат от неагрегата

Логика GROUP BY ALL держится на одном решении: для каждого элемента SELECT — он агрегатный или нет? DuckDB определяет это так: элемент считается агрегатным, если он содержит вызов агрегатной функции (sum, count, avg, min, max, string_agg и т. д.). Всё остальное — неагрегатное и идёт в ключи группировки.

Важно, что проверка рекурсивная и работает по всему выражению. Рассмотрим граничные случаи:

-- amount * 2 — НЕ агрегат, попадёт в ключи группировки
-- sum(amount) — агрегат, в ключи не попадёт
SELECT amount * 2 AS double_amount, sum(qty) AS total_qty
FROM sales
GROUP BY ALL;
-- эквивалент: GROUP BY amount * 2

-- sum(amount) / count(*) — целиком агрегатное выражение
-- (оба под-вызова агрегатные), в ключи группировки НЕ идёт
SELECT region, sum(amount) / count(*) AS avg_amount
FROM sales
GROUP BY ALL;
-- эквивалент: GROUP BY region

Во втором запросе выражение sum(amount) / count(*) — это деление, но оба операнда агрегатные, и DuckDB видит выражение целиком как агрегатное. Оно не попадает в GROUP BY. Ключ группировки — только region.

NOTE

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

Что произойдёт, если в SELECT вообще нет агрегатов? Тогда GROUP BY ALL группирует по всем колонкам SELECT — это эквивалентно SELECT DISTINCT:

-- Нет агрегатов: GROUP BY ALL = группировка по всем колонкам = DISTINCT
SELECT region, product FROM sales GROUP BY ALL;
-- то же, что: SELECT DISTINCT region, product FROM sales;

Полезно зафиксировать ещё одно следствие. Поскольку GROUP BY ALL разворачивается на этапе биндинга — до планирования и оптимизации — оптимизатор и физический планировщик видят точно такое же дерево, как если бы вы написали GROUP BY явно. Это значит, что GROUP BY ALL не имеет ни малейшей стоимости в рантайме: это не «умный оператор, который что-то решает во время выполнения», а чисто текстовая подстановка на ранней стадии компиляции запроса. План, который покажет EXPLAIN, для запроса с GROUP BY ALL и для эквивалентного запроса с явным GROUP BY будет идентичным до последнего узла. Выбор между ними — исключительно вопрос читаемости исходного кода, а не производительности.

Есть и граница применимости, о которой важно помнить. GROUP BY ALL корректно работает, когда SELECT чисто делится на агрегаты и неагрегатные ключи. Если в SELECT присутствует оконная функция, картина усложняется: оконные функции вычисляются уже после группировки, агрегатами они не считаются, и выражение с оконной функцией GROUP BY ALL попытается затащить в ключи группировки — что почти наверняка не то, что вам нужно. Для запросов, смешивающих агрегаты и оконные функции, надёжнее указывать GROUP BY явным списком: так вы полностью контролируете, что именно стало ключом, и не зависите от того, как GROUP BY ALL классифицирует пограничное выражение.


ORDER BY ALL

ORDER BY ALL устроен проще: он сортирует результат по всем колонкам SELECT, слева направо, в порядке их следования. Это удобно для получения детерминированного, стабильно упорядоченного вывода — например, при сравнении результатов двух запросов или при подготовке снапшота для теста.

-- Сортировка по region, затем product, затем revenue
SELECT region, product, sum(amount) AS revenue
FROM sales
GROUP BY ALL
ORDER BY ALL;
-- эквивалент: ORDER BY region, product, revenue

ORDER BY ALL поддерживает модификаторы DESC и NULLS LAST, которые применяются ко всем колонкам сразу:

-- Все колонки по убыванию
SELECT region, sum(amount) AS revenue
FROM sales
GROUP BY ALL
ORDER BY ALL DESC;

В отличие от GROUP BY ALL, у ORDER BY ALL нет умной логики отделения агрегатов — он буквально берёт все выходные колонки по порядку. Поэтому он полезен в основном для двух сценариев: детерминированный вывод небольших результатов и быстрая сортировка при интерактивном исследовании. Для боевых запросов, где важен конкретный порядок сортировки, по-прежнему пишут явный ORDER BY с нужными колонками — ORDER BY ALL не даёт указать разное направление для разных колонок.

GROUP BY ALL отделяет агрегаты, ORDER BY ALL — нет
GROUP BY ALLАнализирует SELECT и исключает агрегатные выражения из ключей группировки — берёт только неагрегатные колонки.
vs
ORDER BY ALLНикакого анализа: буквально берёт все выходные колонки SELECT по порядку слева направо, включая агрегатные.
WARNING

ORDER BY ALL на большом результате — это полная сортировка по всем колонкам, и она может быть дорогой. Сортировка не бесплатна: она материализует и упорядочивает весь результат. Не добавляйте ORDER BY ALL к запросу, который возвращает миллионы строк, если порядок вам на самом деле не нужен.


Сравнение двух конструкций

СвойствоGROUP BY ALLORDER BY ALL
Что выводитНеагрегатные колонки SELECTВсе колонки SELECT
Отделяет агрегатыДа, исключает их из ключейНет, берёт всё подряд
Когда разворачиваетсяНа этапе биндингаНа этапе биндинга
Главная пользаУбирает дубликат SELECT/GROUP BYДетерминированный вывод
Когда лучше писать явноЗапросы с оконными функциямиКогда нужны разные направления сортировки

Главная ценность — у GROUP BY ALL. Он устраняет настоящий дубликат: список группировки перестаёт быть копией SELECT, которую надо синхронизировать руками. Это убирает целый класс ошибок «забыл добавить колонку в GROUP BY» и делает diff при правке запроса чище — меняется только SELECT. ORDER BY ALL — более узкий инструмент, удобный для интерактивной работы и тестов, но в боевом коде его применяют реже.

Стоит обратить внимание, почему именно GROUP BY страдает от дублирования сильнее других клауз. Связь SELECT и GROUP BY — не стилистическая, а семантически обязательная: SQL физически не может вывести неагрегатную колонку, не входящую в ключ группировки, потому что для группы с несколькими разными значениями этой колонки непонятно, какое из них показать. То есть неагрегатная часть SELECT и список GROUP BY обязаны совпадать всегда, по правилам языка, — это не два независимых списка, а один список, записанный дважды. Любая ситуация «одна и та же информация записана в двух местах, и они обязаны быть синхронны» — это приглашение к ошибке: рано или поздно правят одно место и забывают другое. GROUP BY ALL устраняет именно эту структурную проблему — он делает производную информацию (ключи группировки) действительно производной, вычисляемой из единственного источника истины — списка SELECT. В этом смысле GROUP BY ALL ближе к принципу «не повторяйся» из инженерии кода, чем к синтаксическому сахару.


Попробуй сам

Создайте таблицу:

CREATE TABLE sales AS
  SELECT * FROM (VALUES
    ('EU', 'A', DATE '2026-01-15', 120),
    ('EU', 'A', DATE '2026-02-10', 80),
    ('US', 'B', DATE '2026-01-20', 200),
    ('US', 'B', DATE '2026-03-05', 150),
  ) t(region, product, order_date, amount);

Задания:

  1. Напишите запрос с region, product, sum(amount) и count(*), используя GROUP BY ALL. Затем перепишите его с явным GROUP BY и убедитесь в идентичности результата.
  2. Добавьте в SELECT колонку extract(month FROM order_date) AS mon. Запрос с GROUP BY ALL должен заработать без правки GROUP BY. Подумайте, сколько строк пришлось бы поменять в варианте с явным GROUP BY.
  3. Напишите SELECT region, product FROM sales GROUP BY ALL без агрегатов. Сравните результат с SELECT DISTINCT region, product.
  4. Добавьте к запросу из задания 1 ORDER BY ALL DESC и посмотрите на порядок строк.
  5. Объясните себе, почему выражение sum(amount) / count(*) не попадает в ключи группировки при GROUP BY ALL.
Основы GROUP BY: ключи группировки и агрегатные функции
Проверка знанийKnowledge check
По какому правилу GROUP BY ALL определяет, какие элементы SELECT станут ключами группировки, и что произойдёт, если в SELECT нет ни одного агрегата?
ОтветAnswer
GROUP BY ALL анализирует каждый элемент списка SELECT и проверяет рекурсивно по всему выражению, содержит ли оно вызов агрегатной функции (sum, count, avg, min, max, string_agg и т. д.). Если содержит — элемент считается агрегатным и в ключи группировки не попадает. Всё остальное считается неагрегатным и становится ключом GROUP BY. Проверка работает по всему выражению целиком: amount * 2 неагрегатно и идёт в ключи, а sum(amount) / count(*) целиком агрегатно (оба операнда — агрегаты) и в ключи не идёт. Разворачивание происходит на этапе биндинга, до планирования, поэтому план и производительность не отличаются от запроса с явным GROUP BY. Если же в SELECT нет ни одного агрегата, GROUP BY ALL группирует по всем колонкам SELECT — это в точности эквивалентно SELECT DISTINCT. Главная польза конструкции в том, что список группировки перестаёт быть дубликатом неагрегатной части SELECT, который приходилось синхронизировать вручную.

Проверьте понимание

Результат: 0 из 0
Концептуальный
Вопрос 1 из 4. По какому правилу GROUP BY ALL выбирает ключи группировки из списка SELECT?

Закончили урок?

Отметьте его как пройденный, чтобы отслеживать свой прогресс

Войдите чтобы оценить урок

Прогресс модуля
0 из 7