Перейти к основному содержимому
Версия: 7.0

Группировка (GROUP)

Оператор группировки создает свойство, которое разбивает все наборы объектов в системе на группы, и с учетом заданного порядка вычисляет для каждой такой группы агрегирующую функцию. Соответственно, множество, на котором вычисляется эта агрегирующая функция, определяется как все наборы объектов, принадлежащие данной группе. Если набор группировок пуст и оператор не использует верхних параметров, все подходящие наборы объектов образуют одну общую группу. Используемые оператором верхние параметры неявно расширяют набор группировок, так что каждая комбинация их значений образует отдельную группу.

Группы в этом операторе задаются как множество свойств (группировок), порядок задается как список свойств и признак возрастания или убывания. Набор объектов, для которого хотя бы одна из группировок равна NULL, не входит ни в одну группу и не участвует в вычислении ни одного результата. Если агрегирующая функция не коммутативна, то порядок должен быть однозначно определяемым. Для коммутативных агрегаторов порядок сам по себе не влияет на итоговое значение, но становится важным, если агрегирование выполняется не по всей группе, а только по некоторому упорядоченному фрагменту группы.

При этом для группировки полезно различать ключ группы и элементы группы. Ключ группы определяет, какие наборы объектов попадают в одну группу и для какого набора значений существует результат, а элементы группы определяют, по каким данным этот результат вычисляется. Это различие помогает понять, почему группировка возвращает одно значение на группу, а не отдельное значение для каждого исходного набора объектов, как в разбиении / упорядочивании.

Функционально вычисление группировки можно представить так:

  • для каждой группы определяется множество всех принадлежащих ей наборов объектов
  • из этого множества при необходимости отбираются только подходящие элементы
  • если для агрегата важен порядок или по группе нужно взять только ее часть, внутри группы задается порядок
  • при необходимости из упорядоченной группы выбирается только ее фрагмент — например, первые n элементов, все элементы после пропуска первых m, либо следующие n элементов после пропуска первых m
  • по оставшимся данным вычисляется агрегирующая функция

Порядок внутри группы может играть две разные роли. Для некоммутативных агрегаторов (CONCAT, LAST, а также order-sensitive пользовательских агрегатов) он непосредственно влияет на результат, поэтому его следует задавать однозначно. Для коммутативных агрегаторов (SUM, MAX, MIN, EQUAL, AGGR, NAGGR) он используется прежде всего для выбора нужного фрагмента группы.

к сведению

Однозначно определяемого порядка можно гарантированно добиться, если при задании порядка, например, указать идентификаторы всех объектов, для которых выполняется группировка.

Кроме стандартных видов агрегирующих функций для группировки есть три дополнительных вида: EQUAL, AGGR и NAGGR.

  • EQUAL является частным случаем агрегирующей функции MAX (или MIN), добавляя ограничение на одинаковость значения операнда агрегирующей функции внутри каждой группы. Классом результата является класс операнда.
  • AGGR и NAGGR являются частным случаем EQUAL, но с еще более строгим ограничением: для каждой группы существует не более одного набора объектов, операнд агрегирующей функции является одним из объектов, а группировки включают в себя все остальные объекты. Агрегирующая функция NAGGR отличается от AGGR только тем, что в случае ее использования не создается ограничение (предполагается, что это ограничение следует из семантики самих свойств операндов и/или группировок). Поскольку операнд сам является одним из объектов группы, классом результата является класс этого объекта. Ограничение AGGR проверяется при применении изменений, а два одновременных применения не видят еще не зафиксированных изменений друг друга: если каждое из них создает объект в одной и той же группе, оба проходят проверку, и в группе оказываются два объекта. Чтобы этого не происходило, свойство с AGGR нужно материализовать: оно хранится в ряду таблицы с ключом из значений группировок, поэтому одновременные применения записывают один и тот же ряд и одно из них повторяется после конфликта, а при повторе ограничение его отменяет.

Класс SUM — это класс его операнда. Сумма по NUMERIC внутри запроса вычисляется без ограничения точности, поэтому точность её класса проверяется только там, где сумма записывается в колонку, созданную с этим классом: материализованного или локального свойства, объявленного с этим классом, либо временной таблицы подзапроса с этой суммой, когда платформа материализует его при выполнении запроса. Поэтому GROUP SUM по NUMERIC[6,2], итог которого достигает 10000, при одном выполнении запроса вычисляется, а при другом, в котором этот подзапрос материализуется, — завершается ошибкой numeric field overflow. Материализация может включаться при повторе после таймаута, поэтому на одних и тех же данных одно выполнение может пройти, а другое — завершиться ошибкой: чаще на холодном сервере или на большем диапазоне данных, но и после холодного старта не при каждом выполнении. Повторы по таймауту и переходы между шагами материализации записываются в лог sqladjust (Журналы и логи). Приведение результата от этого не спасает, так как в материализованной колонке лежит сама сумма: к более широкому классу приводят операнд внутри агрегата, GROUP SUM NUMERIC[16,2](x).

Пользовательские агрегирующие функции СУБД и пользовательские aggregate-функции, объявленные в базе данных, тоже могут использоваться в группировке. С функциональной точки зрения здесь возможны два разных сценария:

  • обычный пользовательский агрегат вычисляет результат по значениям, которые агрегируются внутри группы, а порядок, если он задан, определяет порядок обработки этих значений
  • ordered-set агрегат вычисляет результат по упорядоченной выборке значений внутри группы, а дополнительные аргументы выступают параметрами самой функции, а не элементами этой выборки. К таким агрегатам относятся, например, процентильные функции

Класс результата пользовательского агрегата может быть задан явно. Иначе он выводится из значений, по которым ведётся агрегирование: из агрегируемых значений операндов для обычного пользовательского агрегата, либо из упорядоченной выборки для ordered-set агрегата.

Язык​

Для объявления свойства, реализующего группировку, используется оператор GROUP.

Примеры​

CLASS Game;
CLASS Team;
hostGoals = DATA INTEGER (Game);
hostTeam = DATA Team (Game);
date = DATA DATE (Game);
// сумма по всей группе
hostGoalsScored(team) = GROUP SUM hostGoals(Game game) BY hostTeam(game);
// сумма только по трем последним играм в группе
last3HostGoalsScored(team) = GROUP SUM hostGoals(Game game) ORDER DESC date(game), game TOP 3 BY hostTeam(game);

name = DATA STRING[100] (Country);
// получается свойство (STRING[100]) -> Country
countryName = GROUP AGGR Country country BY name(country);

CLASS Book;
CLASS Tag;
name = DATA STRING[100] (Tag);
in = DATA BOOLEAN (Book, Tag);
// пример order-dependent агрегатора с фильтрацией внутри группы
tags(Book b) = GROUP CONCAT name(Tag t) IF in(b, t), ', ' ORDER name(t), t;

value = DATA NUMERIC[14,2] (INTEGER);
// ordered-set aggregate: 90-й процентиль значений value(i)
percentile90() = GROUP CUSTOM NUMERIC[14,2] 'percentile_cont' 0.9 WITHIN ORDER value(INTEGER i);