Оператор 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'
. В этом случае 'totals' рассчитываются по всем строкам, включая те, которые не проходят через HAVING и max_rows_to_group_by
.
Другие альтернативы включают только строки, прошедшие через HAVING, в 'totals', и ведут себя по-разному с настройками max_rows_to_group_by
и group_by_overflow_mode = 'any'
.
after_having_exclusive
– Не включать строки, которые не прошли max_rows_to_group_by
. Другими словами, 'totals' будет содержать меньше или столько же строк, сколько было бы, если бы max_rows_to_group_by
не было.
after_having_inclusive
– Включить все строки, которые не прошли через 'max_rows_to_group_by' в 'totals'. Другими словами, 'totals' будет иметь больше или столько же строк, сколько было бы, если бы max_rows_to_group_by
не было.
after_having_auto
– Подсчитать количество строк, которые прошли через HAVING. Если оно превышает определенное количество (по умолчанию 50%), включите все строки, которые не прошли через 'max_rows_to_group_by' в 'totals'. В противном случае не включайте их.
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
).