Клаузула GROUP BY
Клаузула GROUP BY
переводит запрос SELECT
в режим агрегации, который работает следующим образом:
- Клаузула
GROUP BY
содержит список выражений (или одно выражение, которое рассматривается как список длины один). Этот список выступает в качестве "ключа группировки", в то время как каждое отдельное выражение будет обозначаться как "ключевое выражение". - Все выражения в SELECT, HAVING и ORDER BY должны рассчитываться на основе ключевых выражений или на основе агрегатных функций над неключевыми выражениями (включая простые колонки). Иными словами, каждая колонка, выбранная из таблицы, должна использоваться либо в ключевом выражении, либо внутри агрегатной функции, но не и там, и там.
- Результат агрегирования запроса
SELECT
будет содержать столько строк, сколько было уникальных значений "ключа группировки" в исходной таблице. Обычно это значительно уменьшает количество строк, часто на порядки, но не обязательно: количество строк остается тем же, если все значения "ключа группировки" были различными.
Когда вы хотите группировать данные в таблице по номерам колонок вместо имен колонок, включите настройку enable_positional_arguments.
Существует дополнительный способ выполнить агрегацию по таблице. Если запрос содержит только колонки таблицы внутри агрегатных функций, клаузулу GROUP BY
можно опустить, и предполагается агрегация по пустому множеству ключей. Такие запросы всегда возвращают ровно одну строку.
Обработка NULL
Для группировки ClickHouse интерпретирует NULL как значение, и NULL==NULL
. Это отличается от обработки NULL
в большинстве других контекстов.
Вот пример, чтобы показать, что это значит.
Предположим, у вас есть эта таблица:
Запрос SELECT sum(x), y FROM t_null_big GROUP BY y
дает следующий результат:
Вы можете видеть, что GROUP BY
для y = NULL
суммировал x
, как если бы NULL
было этим значением.
Если вы передаете несколько ключей в GROUP BY
, результат выдаст все комбинации выбора, как если бы NULL
было конкретным значением.
Модификатор ROLLUP
Модификатор ROLLUP
используется для расчета промежуточных сумм для ключевых выражений, основываясь на их порядке в списке GROUP BY
. Промежуточные суммы добавляются после таблицы результата.
Промежуточные суммы рассчитываются в обратном порядке: сначала промежуточные суммы рассчитываются для последнего ключевого выражения в списке, затем для предыдущего и так далее до первого ключевого выражения.
В строках промежуточных сумм значения уже "группированных" ключевых выражений устанавливаются в 0
или пустую строку.
Имейте в виду, что клаузула HAVING может повлиять на результаты промежуточных сумм.
Пример
Рассмотрим таблицу t:
Запрос:
Поскольку секция GROUP BY
имеет три ключевых выражения, результат содержит четыре таблицы с промежуточными суммами, "собранными" справа налево:
GROUP BY year, month, day
;GROUP BY year, month
(и колонкаday
заполнена нулями);GROUP BY year
(теперь колонкиmonth, day
обе заполнены нулями);- и итоги (все три ключевых колонки выражения нули).
Тот же запрос также может быть записан с использованием ключевого слова WITH
.
Смотрите также
- Настройка group_by_use_nulls для совместимости со стандартом SQL.
Модификатор CUBE
Модификатор CUBE
используется для расчета промежуточных сумм для каждой комбинации ключевых выражений в списке GROUP BY
. Промежуточные суммы добавляются после таблицы результата.
В строках промежуточных сумм значения всех "группированных" ключевых выражений устанавливаются в 0
или пустую строку.
Имейте в виду, что клаузула HAVING может повлиять на результаты промежуточных сумм.
Пример
Рассмотрим таблицу t:
Запрос:
Поскольку секция GROUP BY
имеет три ключевых выражения, результат содержит восемь таблиц с промежуточными суммами для всех комбинаций ключевых выражений:
GROUP BY year, month, day
GROUP BY year, month
GROUP BY year, day
GROUP BY year
GROUP BY month, day
GROUP BY month
GROUP BY day
- и итоги.
Колонки, исключенные из GROUP BY
, заполнены нулями.
Тот же запрос также может быть записан с использованием ключевого слова WITH
.
Смотрите также
- Настройка group_by_use_nulls для совместимости со стандартом SQL.
Модификатор WITH TOTALS
Если указан модификатор WITH TOTALS
, будет вычислена еще одна строка. Эта строка будет иметь ключевые колонки, содержащие значения по умолчанию (нули или пустые строки), и колонки агрегатных функций с значениями, рассчитанными по всем строкам (значения "итогов").
Эта дополнительная строка выводится только в форматах JSON*
, TabSeparated*
и Pretty*
, отдельно от других строк:
- В форматах
XML
иJSON*
эта строка выводится как отдельное поле 'totals'. - В форматах
TabSeparated*
,CSV*
иVertical
строка идет после основного результата, предшествуемая пустой строкой (после других данных). - В форматах
Pretty*
строка выводится как отдельная таблица после основного результата. - В формате
Template
строка выводится в соответствии с указанным шаблоном. - В других форматах она недоступна.
Итоги выводятся в результатах запросов SELECT
, но не выводятся в INSERT INTO ... SELECT
.
WITH TOTALS
может выполняться разными способами, когда присутствует HAVING. Поведение зависит от настройки totals_mode
.
Настройка обработки итогов
По умолчанию totals_mode = 'before_having'
. В этом случае 'итоги' рассчитываются по всем строкам, включая те, которые не прошли через HAVING и max_rows_to_group_by
.
Другие альтернативы включают только строки, которые проходят через HAVING в 'итогах', и ведут себя по-разному с настройками max_rows_to_group_by
и group_by_overflow_mode = 'any'
.
after_having_exclusive
– Не включать строки, которые не прошли через max_rows_to_group_by
. Иными словами, 'итоги' будут содержать меньшее или то же количество строк, как если бы max_rows_to_group_by
был опущен.
after_having_inclusive
– Включать все строки, которые не прошли через 'max_rows_to_group_by' в 'итоги'. Иными словами, 'итоги' будут содержать большее или то же количество строк, как если бы max_rows_to_group_by
был опущен.
after_having_auto
– Подсчитать количество строк, которые прошли через HAVING. Если это больше определенного количества (по умолчанию 50%), включить все строки, которые не прошли через 'max_rows_to_group_by' в 'итоги'. В противном случае, не включать их.
totals_auto_threshold
– По умолчанию 0.5. Коэффициент для after_having_auto
.
Если max_rows_to_group_by
и group_by_overflow_mode = 'any'
не используются, все варианты after_having
одинаковы, и вы можете использовать любой из них (например, after_having_auto
).
Вы можете использовать WITH TOTALS
в подзапросах, включая подзапросы в клаузе JOIN (в этом случае соответствующие итоговые значения комбинируются).
GROUP BY ALL
GROUP BY ALL
эквивалентно перечислению всех выражений SELECT, которые не являются агрегатными функциями.
Например:
эквивалентно
В специальном случае, если имеется функция, имеющая как агрегатные функции, так и другие поля в качестве аргументов, ключи GROUP BY
будут содержать максимальные неагрегатные поля, которые мы можем извлечь из нее.
Например:
эквивалентно
Примеры
Пример:
В отличие от MySQL (и в соответствии со стандартом SQL), вы не можете получить какое-либо значение определенной колонки, которое не находится в ключе или агрегатной функции (за исключением постоянных выражений). Чтобы обойти это, вы можете использовать агрегатную функцию 'any' (получить первое встретившееся значение) или 'min/max'.
Пример:
Для каждого встреченного уникального значения ключа GROUP BY
вычисляет набор значений агрегатных функций.
Модификатор GROUPING SETS
Это самый общий модификатор. Этот модификатор позволяет вручную определить несколько наборов ключей агрегации (наборы группировки). Агрегация выполняется отдельно для каждого набора группировки, а затем все результаты комбинируются. Если колонка не представлена в наборе группировки, она заполняется значением по умолчанию.
Иными словами, модификаторы, описанные выше, могут быть представлены через GROUPING SETS
. Несмотря на то, что запросы с модификаторами ROLLUP
, CUBE
и GROUPING SETS
синтаксически равны, они могут работать по-разному. Когда GROUPING SETS
пытается выполнять все в параллельном режиме, ROLLUP
и CUBE
выполняют окончательное объединение агрегатов в одном потоке.
В ситуации, когда исходные колонки содержат значения по умолчанию, может быть трудно отличить, является ли строка частью агрегации, которая использует эти колонки в качестве ключей или нет. Чтобы решить эту проблему, необходимо использовать функцию GROUPING
.
Пример
Следующие два запроса эквивалентны.
Смотрите также
- Настройка group_by_use_nulls для совместимости со стандартом SQL.
Детали реализации
Агрегация является одной из самых важных функций столбцовой СУБД, и, следовательно, ее реализация является одной из самых оптимизированных частей ClickHouse. По умолчанию агрегация выполняется в памяти с использованием хеш-таблицы. Она имеет более 40 специализаций, которые выбираются автоматически в зависимости от типов данных "ключа группировки".
Оптимизация GROUP BY в зависимости от ключа сортировки таблицы
Агрегация может выполняться более эффективно, если таблица отсортирована по какому-то ключу, и выражение GROUP BY
содержит по крайней мере префикс ключа сортировки или инъективные функции. В этом случае, когда новый ключ читается из таблицы, промежуточный результат агрегации может быть завершен и отправлен клиенту. Это поведение включается настройкой optimize_aggregation_in_order. Такая оптимизация уменьшает использование памяти во время агрегации, но в некоторых случаях может замедлить выполнение запроса.
GROUP BY во внешней памяти
Вы можете включить выгрузку временных данных на диск, чтобы ограничить использование памяти во время GROUP BY
.
Настройка max_bytes_before_external_group_by определяет порог потребления ОЗУ для выгрузки временных данных GROUP BY
в файловую систему. Если установлено значение 0 (по умолчанию), это отключено.
В качестве альтернативы, вы можете установить max_bytes_ratio_before_external_group_by, что позволяет использовать GROUP BY
во внешней памяти только после достижения определенного порога использования памяти.
При использовании max_bytes_before_external_group_by
мы рекомендуем устанавливать max_memory_usage
примерно в два раза выше (или max_bytes_ratio_before_external_group_by=0.5
). Это необходимо, потому что есть два этапа агрегации: чтение данных и формирование промежуточных данных (1) и слияние промежуточных данных (2). Выгрузка данных в файловую систему может происходить только на этапе 1. Если временные данные не были выгружены, то этап 2 может потребовать до такого же количества памяти, как и на этапе 1.
Например, если max_memory_usage была установлена на 10000000000, и вы хотите использовать внешнюю агрегацию, имеет смысл установить max_bytes_before_external_group_by
на 10000000000 и max_memory_usage
на 20000000000. Когда внешняя агрегация включается (если была хотя бы одна выгрузка временных данных), максимальное потребление ОЗУ составляет всего лишь немного больше max_bytes_before_external_group_by
.
При распределенной обработке запросов внешняя агрегация выполняется на удаленных серверах. Чтобы запрашивающий сервер использовал только небольшое количество ОЗУ, установите distributed_aggregation_memory_efficient
в 1.
При слиянии данных, сброшенных на диск, а также при слиянии результатов с удаленных серверов, когда включена настройка distributed_aggregation_memory_efficient
, потребляется до 1/256 * количество потоков
от общего объема ОЗУ.
Когда внешняя агрегация включена, если данных было меньше max_bytes_before_external_group_by
(то есть данные не были сброшены), запрос выполняется так же быстро, как и без внешней агрегации. Если какие-либо временные данные были сброшены, время выполнения будет в несколько раз дольше (примерно в три раза).
Если у вас есть ORDER BY с LIMIT после GROUP BY
, тогда количество используемой ОЗУ зависит от количества данных в LIMIT
, а не от всей таблицы. Но если ORDER BY
не имеет LIMIT
, не забудьте включить внешнюю сортировку (max_bytes_before_external_sort
).