---
metadata:
  - name: generator
    content: Diplodoc Platform v5.50.4
alternate:
  - https://ydb.tech/docs/en/yql/reference/syntax/select/group-by.md
  - https://ydb.tech/docs/ru/yql/reference/syntax/select/group-by.md
sourcePath: ru/core/yql/reference/syntax/select/group-by.md
---
> **Documentation Index:** Fetch the complete configuration index at https://ydb.tech/docs/ru/llms.txt


## GROUP BY

Группирует результаты `SELECT` по значениям указанных столбцов или выражений. Вместе с `GROUP BY` часто применяются [агрегатные функции](https://ydb.tech/docs/ru/yql/reference/builtins/aggregation.md) (`COUNT`, `MAX`, `MIN`, `SUM`, `AVG`) для выполнения вычислений в каждой группе.

Если `GROUP BY` присутствует в запросе, то при выборке столбцов (между `SELECT ... FROM`) допустимы следующие конструкции:

1. Столбцы, по которым производится группировка (присутствующие в аргументе `GROUP BY`).
2. Агрегатные функции (см. следующий раздел). Столбцы, по которым **не** идёт группировка, можно включать только в качестве аргументов агрегатной функции.
3. Функции, выдающие начальное и конечное время текущего окна (`HOP_START` и `HOP_END`) (для `GROUP BY HOP`).
4. Произвольные вычисления, комбинирующие пункты 1-3.

Имеется возможность выполнять группировку по результату вычисления произвольного выражения от исходных столбцов. В этом случае для получения доступа к результату этого выражения рекомендуется присваивать ему имя с помощью `AS`, см. второй [пример](#examples).


### Синтаксис

```yql
SELECT                             -- В SELECT можно использовать:
    column1,                       -- ключевые колонки, заданные в GROUP BY
    key_n,                         -- именованные выражения, заданные в GROUP BY
    column1 + key_n,               -- произвольные неагрегатные функции от них
    Aggr_Func1( column2 ),         -- агрегатные функции, содержащие в аргументах любые колонки,
    Aggr_Func2( key_n + column2 ), --   включая именованные выражения, заданные в GROUP BY
    ...
FROM table
GROUP BY
    column1, column2, ...,
    <expr> AS key_n           -- При группировке по выражению ему может быть задано имя через AS,
                              -- которое может быть использовано в SELECT
```

Запрос вида `SELECT * FROM table GROUP BY k1, k2, ...` вернет все колонки, перечисленные в GROUP BY, то есть эквивалентен запросу `SELECT DISTINCT k1, k2, ... FROM table`.

Звездочка может также применяться в качестве аргумента агрегатной функции `COUNT`. `COUNT(*)` означает "число строк в группе".


{% note info %}

Агрегатные функции не учитывают `NULL` в своих аргументах, за исключением функции `COUNT`.

{% endnote %}

Также в YQL доступен механизм фабрик агрегатных функций, реализованный с помощью функций [`AGGREGATION_FACTORY`](https://ydb.tech/docs/ru/yql/reference/builtins/basic.md#aggregationfactory) и [`AGGREGATE_BY`](https://ydb.tech/docs/ru/yql/reference/builtins/aggregation.md#aggregateby).

### Примеры {#examples}

```yql
SELECT key, COUNT(*) FROM my_table
GROUP BY key;
```

```yql
SELECT double_key, COUNT(*) FROM my_table
GROUP BY key + key AS double_key;
```

```yql
SELECT
   double_key,                           -- ОК: ключевая колонка
   COUNT(*) AS group_size,               -- OK: COUNT(*)
   SUM(key + subkey) AS sum1,            -- ОК: агрегатная функция
   CAST(SUM(1 + 2) AS String) AS sum2,   -- ОК: агрегатная функция с константным аргументом
   SUM(SUM(1) + key) AS sum3,            -- ОШИБКА: вложенные агрегации не допускаются
   key AS k1,                            -- ОШИБКА: использование неключевой колонки key без агрегации
   key * 2 AS dk1,                       -- ОШИБКА в YQL: использование неключевой колонки key без агрегации
FROM my_table
GROUP BY
  key * 2 AS double_key,
  subkey as sk,

```


{% note warning %}

Возможность указывать имя для колонки или выражения в `GROUP BY .. AS foo` является расширением YQL. Такое имя становится видимым в `WHERE` несмотря на то, что фильтрация по `WHERE` выполняется [раньше](https://ydb.tech/docs/ru/yql/reference/syntax/index.md#selectexec) группировки. В частности, если в таблице `T` есть две колонки `foo` и `bar`, то в запросе `SELECT foo FROM T WHERE foo > 0 GROUP BY bar AS foo` фильтрация фактически произойдет по колонке `bar` из исходной таблицы.

{% endnote %}


## GROUP BY ... SessionWindow() {#session-window}

В YQL поддерживаются группировки по сессиям. К обычным выражениям в `GROUP BY` можно добавить специальную функцию `SessionWindow`:

```yql
SELECT
  user,
  session_start,
  SessionStart() AS same_session_start, -- то же что и session_start
  COUNT(*) AS session_size,
  SUM(value) AS sum_over_session,
FROM my_table
GROUP BY user, SessionWindow(<time_expr>, <timeout_expr>) AS session_start
```

При этом происходит следующее:

1. Входная таблица партиционируется по ключам группировки, указанным в `GROUP BY`, без учета SessionWindow (в данном случае по `user`). Если кроме SessionWindow в `GROUP BY` ничего нет, то входная таблица попадает в одну партицию
2. Каждая партиция делится на непересекающие подмножества строк (сессии). Для этого партиция сортируется по возрастанию значения выражения `time_expr`. Границы сессий проводятся между соседними элементами партиции, разница значений `time_expr` для которых превышает `timeout_expr`
3. Полученные таким образом сессии и являются финальными партициями, на которых вычисляются агрегатные функции.

Ключевая колонка SessionWindow() (в примере `session_start`) имеет значение "минимальный `time_expr` в сессии".
Кроме того, при наличии SessionWindow() в `GROUP BY` может использоваться специальная агрегатная функция
[SessionStart](https://ydb.tech/docs/ru/yql/reference/builtins/aggregation.md#session-start).

Поддерживается также расширенный вариант SessionWindow с четырьмя аргументами:

`SessionWindow(<order_expr>, <init_lambda>, <update_lambda>, <calculate_lambda>)`

Здесь:

* `<order_expr>` – выражение по которому сортируется исходная партиция
* `<init_lambda>` – лямбда-функция для инициализации состояния расчета сессий. Имеет сигнатуру `(TableRow())->State`. Вызывается один раз на первом (по порядку сортировки) элементе исходной партиции
* `<update_lambda>` – лямбда-функция для обновления состояния расчета сессий и определения границ сессий. Имеет сигнатуру `(TableRow(), State)->Tuple<Bool, State>`. Вызывается на каждом элементе исходной партиции, кроме первого. Новое значения состояния вычисляется на основе текущей строки таблицы и предыдущего состояния. Если первый элемент возвращенного кортежа имеет значение `True`, то с _текущей_ строки начнется новая сессия. Ключ новой сессии получается путем применения `<calculate_lambda>` ко второму элементу кортежа.
* `<calculate_lambda>` – лямбда-функция для вычисления ключа сессии ("значения" SessionWindow(), которое также доступно через SessionStart()). Функция имеет сигнатуру `(TableRow(), State)->SessionKey`. Вызывается на первом элемента партиции (после `<init_lambda>`) и на тех элементах, для которых `<update_lambda>` вернула `True` в качестве первого элемента кортежа. Стоит отметить, что для начала новой сессии необходимо, чтобы `<calculate_lambda>` вернула значение, которое отличается от предыдущего ключа сессии. При этом сессии с одинаковыми ключами не объединяются. Например, если `<calculate_lambda>` последовательно возвращает `0, 1, 0, 1`, то это будут четыре различные сессии.

С помощью расширенного варианта SessionWindow можно решить, например, такую задачу: разделить партицию на сессии как в варианте SessionWindow с двумя аргументами, но с ограничением максимальной длины сессии некоторой константой:

### Пример

```yql
$max_len = 1000; -- максимальная длина сессии
$timeout = 100; -- таймаут (timeout_expr в упрощенном варианте SessionWindow)

$init = ($row) -> (AsTuple($row.ts, $row.ts)); -- состояние сессии - тапл из 1) значения временной колонки ts на первой строчке сессии и 2) на текущей строчке
$update = ($row, $state) -> {
  $is_end_session = $row.ts - $state.0 > $max_len OR $row.ts - $state.1 > $timeout;
  $new_state = AsTuple(IF($is_end_session, $row.ts, $state.0), $row.ts);
  return AsTuple($is_end_session, $new_state);
};
$calculate = ($row, $state) -> ($row.ts);
SELECT
  user,
  session_start,
  SessionStart() AS same_session_start, -- то же что и session_start
  COUNT(*) AS session_size,
  SUM(value) AS sum_over_session,
FROM my_table
GROUP BY user, SessionWindow(ts, $init, $update, $calculate) AS session_start
```

`SessionWindow` может использоваться в `GROUP BY` только один раз.



  ## ROLLUP, CUBE и GROUPING SETS {#rollup}

Результаты вычисления агрегатной функции в виде промежуточных итогов для групп и общих итогов для отдельных столбцов или всей таблицы.

### Синтаксис

```yql
SELECT
    c1, c2,                          -- столбцы, по которым производится группировка

AGGREGATE_FUNCTION(c3) AS outcome_c  -- агрегатная функция (SUM, AVG, MIN, MAX, COUNT)

FROM table_name

GROUP BY
    GROUP_BY_EXTENSION(c1, c2)       -- расширение GROUP BY: ROLLUP, CUBE или GROUPING SETS
```

* `ROLLUP` — группирует значения столбцов в порядке их перечисления в аргументах (строго слева направо), формирует промежуточные итоги для каждой группы и общий итог.
* `CUBE` — группирует значения для всех возможных комбинаций столбцов, формирует промежуточные итоги для каждой группы и общий итог.
* `GROUPING SETS` — задает группы для промежуточных итогов.

`ROLLUP`, `CUBE` и `GROUPING SETS` можно комбинировать через запятую.

### GROUPING {#grouping}

В промежуточном итоге значения столбцов, которые не участвуют в вычислениях, заменяются на `NULL`. В общем итоге на `NULL` заменяются значения всех столбцов. `GROUPING` — функция, которая позволяет отличить исходные значения `NULL` от `NULL`, которые были добавлены при формировании общих и промежуточных итогов.

`GROUPING` возвращает битовую маску:

* `0` — `NULL` для исходного пустого значения.
* `1` — `NULL`, добавленный для промежуточного или общего итога.

### Пример

```yql
SELECT
    column1,
    column2,
    column3,

    CASE GROUPING(
        column1,
        column2,
        column3,
    )
        WHEN 1  THEN "Subtotal: column1 and column2"
        WHEN 3  THEN "Subtotal: column1"
        WHEN 4  THEN "Subtotal: column2 and column3"
        WHEN 6  THEN "Subtotal: column3"
        WHEN 7  THEN "Grand total"
        ELSE         "Individual group"
    END AS subtotal,

    COUNT(*) AS rows_count

FROM my_table

GROUP BY
    ROLLUP(
        column1,
        column2,
        column3
    ),
    GROUPING SETS(
        (column2, column3),
        (column3)
        -- если добавить сюда ещё (column2), то в сумме
        -- эти ROLLUP и GROUPING SETS дали бы результат,
        -- аналогичный CUBE
    )
;
```



## DISTINCT {#distinct}

Применение [агрегатных функций](https://ydb.tech/docs/ru/yql/reference/builtins/aggregation.md) только к уникальным значениям столбца.

{% note info %}

Применение `DISTINCT` к вычислимым значениям на данный момент не реализовано. С этой целью можно использовать [подзапрос](https://ydb.tech/docs/ru/yql/reference/syntax/select/from.md) или выражение `GROUP BY ... AS ...`.

{% endnote %}

### Пример

```yql
SELECT
  key,
  COUNT(DISTINCT value) AS count -- топ-3 ключей по количеству уникальных значений
FROM my_table
GROUP BY key
ORDER BY count DESC
LIMIT 3;
```

Также ключевое слово `DISTINCT` может использоваться для выборки уникальных строк через [`SELECT DISTINCT`](https://ydb.tech/docs/ru/yql/reference/syntax/select/distinct.md).



## COMPACT

Наличие [SQL хинта](https://ydb.tech/docs/ru/yql/reference/syntax/lexer.md#sql-hints) `COMPACT` непосредственно после ключевого слова `GROUP` позволяет более эффективно выполнять агрегацию в тех случаях, когда автору запроса заранее известно, что ни по одному из ключей агрегации не найдется большого количества данных (порядка гигабайта или миллиона строк). Если это предположение не оправдается, то операция может упасть с ошибкой Out of Memory или начать работать значительно медленнее по сравнению с обычным GROUP BY.

В отличие от обычного GROUP BY, отключается стадия Map-side combiner и дополнительные Reduce для каждого поля с [DISTINCT](#distinct) агрегацией.

### Пример

```yql
SELECT
  key,
  COUNT(DISTINCT value) AS count -- топ-3 ключей по количеству уникальных значений
FROM my_table
GROUP /*+ COMPACT() */ BY key
ORDER BY count DESC
LIMIT 3;
```


## GROUP BY ... HOP {#group-by-hop}

`HOP` группирует данные по перекрывающимся временным окнам ([hopping windows](https://en.wikipedia.org/wiki/Window_function_(SQL)#Hopping_window)). Поддерживается как в [аналитических запросах к таблицам](#hop-table), так и в [потоковых запросах к топикам](#hop-topic).

```yql
HOP(time_extractor, hop, interval, delay)
```

Где:

- `time_extractor` — SQL выражение типа `Timestamp`, определяющее время события. Из каждой входной строки вычисляется временная метка, по которой определяется принадлежность к окнам.
- `hop` — шаг между началами соседних окон в формате [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601#Durations), например `"PT10S"` (10 секунд).
- `interval` — размер (длительность) каждого окна в формате ISO 8601, например `"PT30S"` (30 секунд).
- `delay` — задержка закрытия окна после его завершения в формате ISO 8601. Используется только в потоковых запросах (при работе с таблицами игнорируется). Для потоковых запросов рекомендуется использовать [HoppingWindow](#group-by-hopping_window) с [watermarks](https://ydb.tech/docs/ru/dev/streaming-query/watermarks.md) вместо `delay`.

Также доступны агрегатные функции `HOP_START()` и `HOP_END()`, которые возвращают временную метку начала и конца текущего окна типа `Timestamp` соответственно.

### Описание {#hop-description}

Разберём алгоритм на примере.

```yql
GROUP BY HOP(CAST(ts AS Timestamp), "PT10S", "PT30S", "PT20S")
```

В этом примере `CAST(ts AS Timestamp)` извлекает время события из столбца `ts`. Параметр `hop` равен 10 секундам, `interval` равен 30 секундам, `delay` равен 20 секундам.

Окна строятся по следующему правилу:

- Начала окон выравниваются по моментам, кратным `hop` (10 секунд), начиная с 0: 0, 10, 20 и так далее.
- Длительность каждого окна равна `interval` (30 секунд). Получаются окна: `[0; 30)`, `[10; 40)`, `[20; 50)` и так далее.
- Событие попадает во все окна, временной диапазон которых включает его время. Например, событие с временем 25 секунд попадает в окна `[0; 30)`, `[10; 40)` и `[20; 50)`.
- Окно считается завершённым при получении события с временной меткой не меньше, чем конец этого окна + `delay` (20 секунд). Например, окно `[10; 40)` закрывается при получении события с меткой 60 и более.

### Аналитический `HOP` поверх таблицы {#hop-table}

При работе с таблицами данные группируются по ключам `GROUP BY` (без учёта `HOP`), образуя группы строк (далее группы). Внутри каждой группы:

1. Строки сортируются по возрастанию `time_extractor`.
2. Каждая строка назначается в одно или несколько перекрывающихся окон.
3. На каждом окне вычисляются заданные агрегатные функции.

Параметр `delay` при аналитической обработке таблицы **не используется**: данные уже доступны целиком, порядок обхода задаётся сортировкой по `time_extractor`, а завершение окна определяется алгоритмом полного прохода по группе (см. правило закрытия в [описании](#hop-description) выше).

### Потоковый `HOP` поверх топика {#hop-topic}

При работе с [топиками](https://ydb.tech/docs/ru/concepts/datamodel/topic.md) данные группируются по ключам `GROUP BY` (без учёта `HOP`), образуя группы. Внутри каждой группы:

1. События обрабатываются в порядке, близком к возрастанию `time_extractor`. Допускаются небольшие отклонения от строгого порядка.
2. Каждое событие назначается в одно или несколько перекрывающихся окон.
3. На каждом окне вычисляются заданные агрегатные функции.

В потоковых запросах события могут приходить не в строгом хронологическом порядке. Параметр `delay` задаёт время ожидания после формального завершения окна: окно закрывается не сразу, а через `delay` секунд, чтобы дать задержавшимся событиям время на поступление. События, поступившие после закрытия окна, игнорируются.

### Ограничения

`time_extractor` — это SQL выражение, зависящее только от входных значений столбцов, должно иметь тип `Timestamp`.

Для задания `hop`, `interval` и `delay` используется строковое выражение, соответствующее стандарту [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601), например, `PT10S` — 10 секунд, `PT1M` — 1 минута. Это формат, который используется для конструирования встроенного типа `Interval` из [строки](https://ydb.tech/docs/ru/yql/reference/builtins/basic.md#data-type-literals).

Значения параметров `interval` и `delay` должны делиться на значение параметра `hop`. Это требование обеспечивает выравнивание границ окон: каждое окно начинается в момент, кратный `hop`, и заканчивается ровно через `interval`, что гарантирует равномерное покрытие временной оси без пропусков. Параметры `hop` и `interval` должны быть положительными.

### Примеры

```yql
SELECT
    sensor_id,
    HOP_END() AS window_end,
    AVG(temperature) AS avg_temp,
    COUNT(*) AS event_count
FROM sensor_data
GROUP BY
    sensor_id,
    HOP(CAST(event_time AS Timestamp), "PT10S", "PT1M", "PT30S");
```

## GROUP BY ... HoppingWindow {#group-by-hopping_window}

`HoppingWindow` группирует события по перекрывающимся временным окнам (hopping windows), аналогично [GROUP BY HOP](#group-by-hop). Поддерживается как в [аналитических запросах к таблицам](#hopping-window-table), так и в [потоковых запросах к топикам](#hopping-window-topic). Основное отличие от `HOP`: в потоковых запросах `HoppingWindow` использует механизм [watermarks](https://ydb.tech/docs/ru/dev/streaming-query/watermarks.md) для определения момента закрытия окна вместо фиксированного параметра `delay`.

```yql
HoppingWindow(time_extractor, hop, interval)
```

Где:

- `time_extractor` — SQL выражение типа `Timestamp`, определяющее время события. Должно зависеть только от входных столбцов.
- `hop` — шаг (период сдвига) между началами соседних окон в формате [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601#Durations), например `"PT10S"` (10 секунд).
- `interval` — размер (длительность) каждого окна в формате ISO 8601, например `"PT1M"` (1 минута). Значение `interval` должно делиться на `hop`, так как окна выравниваются по кратным интервалам шага.

Также доступны функции `HOP_START()` и `HOP_END()`, возвращающие временные метки начала и конца текущего окна.

Алгоритм построения окон совпадает с [GROUP BY HOP](#group-by-hop): окна начинаются в моменты, кратные `hop`, и имеют длительность `interval`. Событие попадает во все окна, временной диапазон которых включает его время.

### Аналитический `HoppingWindow` поверх таблицы {#hopping-window-table}

При работе с таблицами `HoppingWindow` выполняет группировку по временным окнам аналогично [HOP](#group-by-hop), но без параметра `delay`, который при аналитическом использовании всегда игнорировался (данные в таблице уже отсортированы).

1. Входная таблица партиционируется по ключам группировки, указанным в `GROUP BY`, без учёта `HoppingWindow`.
2. Каждая партиция сортируется по возрастанию `time_extractor`.
3. Каждая партиция делится на перекрывающиеся подмножества событий (окна).
4. На каждом подмножестве вычисляются заданные агрегатные функции.

### Потоковый `HoppingWindow` поверх топика {#hopping-window-topic}

При работе с [топиками](https://ydb.tech/docs/ru/concepts/datamodel/topic.md) `HoppingWindow` использует [watermarks](https://ydb.tech/docs/ru/dev/streaming-query/watermarks.md) для определения момента закрытия окна. Окно закрывается, когда значение watermark не меньше конца окна. Это обеспечивает более точные результаты агрегации по сравнению с `HOP`, где окно закрывается по фиксированному `delay`.

1. Входной топик партиционируется по ключам группировки, указанным в `GROUP BY`, без учёта `HoppingWindow`.
2. В каждой партиции окно продвигается независимо от других.
3. События обрабатываются в порядке, близком к возрастанию `time_extractor`. Допускаются небольшие перестановки порядка входного потока.
4. Каждая партиция делится на перекрывающиеся подмножества событий (окна).
5. Окно закрывается при получении watermark, значение которого не меньше конца окна. После закрытия результат агрегации выдаётся.
6. События, поступившие после закрытия окна, не учитываются в результатах.

Для корректной работы `HoppingWindow` в потоковом режиме необходимо настроить watermark в секции [WITH](https://ydb.tech/docs/ru/yql/reference/syntax/select/with.md) источника. Подробнее: [Настройка](https://ydb.tech/docs/ru/dev/streaming-query/watermarks.md#configuration).

### Пример

Ниже — потоковое чтение из топика: в `SELECT` удобно выводить конец окна через `HOP_END()`. Для **таблиц** в аналитическом запросе чаще используют `HOP_START()` или `HOP_END()` в зависимости от того, какую границу окна нужно показать в результате; смысл окон при этом тот же, отличается только выбираемая метка.

```yql
SELECT
    key,
    HOP_END() AS window_end,
    COUNT(*) AS event_count
FROM
    my_topic
WITH (
    FORMAT = json_each_row,
    SCHEMA = (
        key String,
        event_time String
    ),
    WATERMARK = __ydb_write_time - Interval("PT5S")
)
GROUP BY
    key,
    HoppingWindow(__ydb_write_time, "PT10S", "PT1M");
```

## HAVING {#having}

Фильтрация выборки `SELECT` по результатам вычисления [агрегатных функций](https://ydb.tech/docs/ru/yql/reference/builtins/aggregation.md). Синтаксис аналогичен конструкции [`WHERE`](https://ydb.tech/docs/ru/yql/reference/syntax/select/where.md).

### Пример

```yql
SELECT
    key
FROM my_table
GROUP BY key
HAVING COUNT(value) > 100;
```
