Группировка (GROUP)
Оператор группировки создает свойство, которое разбивает все наборы объектов в системе на группы, и с учетом заданного порядка вычисляет для каждой такой группы агрегирующую функцию. Соответственно, множество, на котором вычисляется эта агрегирующая функция, определяется как все наборы объектов, принадлежащие данной группе. Если набор группировок пуст, все подходящие наборы объектов образуют одну общую группу.
Группы в этом операторе задаются как множество свойств (группировок), порядок задается как список свойств и признак возрастания или убывания. Если агрегирующая функция не коммутативна, то порядок должен быть однозначно определяемым. Для коммутативных агрегаторов порядок сам по себе не влияет на итоговое значение, но становится важным, если агрегирование выполняется не по всей группе, а только по некоторому упорядоченному фрагменту группы.
При этом для группировки полезно различать ключ группы и элементы группы. Ключ группы определяет, какие наборы объектов попадают в одну группу и для какого набора значений существует результат, а элементы группы определяют, по каким данным этот результат вычисляется. Это различие помогает понять, почему группировка возвращает одно значение на группу, а не отдельное значение для каждого исходного набора объектов, как в разбиении / упорядочивании.
Функционально вычисление группировки можно представить так:
- для каждой группы определяется множество всех принадлежащих ей наборов объектов
- из этого множества при необходимости отбираются только подходящие элементы
- если для агрегата важен порядок или по группе нужно взять только ее часть, внутри группы задается порядок
- при необходимости из упорядоченной группы выбирается только ее фрагмент, например после пропуска первых
mэлементов берутся следующиеn - по оставшимся данным вычисляется агрегирующая функция
Порядок внутри группы может играть две разные роли. Для некоммутативных агрегаторов (CONCAT, LAST, а также order-sensitive пользовательских агрегатов) он непосредственно влияет на результат, поэтому его следует задавать однозначно. Для коммутативных агрегаторов (SUM, MAX, MIN, EQUAL, AGGR, NAGGR) он используется прежде всего для выбора нужного фрагмента группы.
Однозначно определяемого порядка можно гарантированно добиться, если при задании порядка, например, указать идентификаторы всех объектов, для которых выполняется группировка.
Кроме стандартных видов агрегирующих функций для группировки есть три дополнительных вида: EQUAL, AGGR и NAGGR.
EQUALявляется частным случаем агрегирующей функцииMAX(илиMIN), добавляя ограничение на одинаковость значения операнда агрегирующей функции внутри каждой группы.AGGRиNAGGRявляются частным случаемEQUAL, но с еще более строгим ограничением: для каждой группы существует не более одного набора объектов, операнд агрегирующей функции является одним из объектов, а группировки включают в себя все остальные объекты. Агрегирующая функцияNAGGRотличается отAGGRтолько тем, что в случае ее использования не создается ограничение (предполагается, что это ограничение следует из семантики самих свойств операндов и/или группировок).
Пользовательские агрегирующие функции СУБД и пользовательские aggregate-функции, объявленные в базе данных, тоже могут использоваться в группировке. С функциональной точки зрения здесь возможны два разных сценария:
- обычный пользовательский агрегат вычисляет результат по значениям, которые агрегируются внутри группы; порядок, если он задан, определяет порядок обработки этих значений
- 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);