Агрегация

Введение

Агрегатные функции — aggr(expr) — выполняются над всеми строками, соответствующими заданному ключу группировки. Ключи сравниваются на эквивалентность.

При стандартной агрегации (то есть при вызове aggr(expr)) список значений, передаваемых в агрегатную функцию, формируется из кандидатных значений после удаления всех NULL.

Примечание!
В YMatrix AGE результаты агрегации не упорядочены. Чтобы получить упорядоченный набор результатов из агрегированных данных, применяйте ORDER BY внешне, то есть в обёртывающем SQL-запросе — за пределами функции cypher(). Указание ORDER BY внутри Cypher-выражения не влияет на глобальный порядок; оно гарантирует упорядоченность только в пределах segment.
Пример:
SELECT * FROM cypher('graph_name', $$ /* aggr(expr) */ $$) AS (grouping_key agtype) ORDER BY grouping_key;

Подготовка данных

SELECT * FROM cypher('graph_name', $$
    CREATE (a:Person {name: 'A', age: 13}),
           (b:Person {name: 'B', age: 33, eyes: "blue"}),
           (c:Person {name: 'C', age: 44, eyes: "blue"}),
           (d1:Person {name: 'D', eyes: "brown"}),
           (d2:Person {name: 'D'}),
           (a)-[:KNOWS]->(b),
           (a)-[:KNOWS]->(c),
           (a)-[:KNOWS]->(d1),
           (b)-[:KNOWS]->(d2),
           (c)-[:KNOWS]->(d2)
$$) AS (a agtype);

Автоматическая группировка (Auto-GROUP BY)

Cypher предоставляет возможности агрегации, аналогичные GROUP BY в SQL. Агрегатные функции принимают набор значений и вычисляют одно сводное значение — например, avg() вычисляет среднее арифметическое числовых значений, а min() возвращает наименьшее числовое или лексикографическое значение в наборе.

Когда говорят, что агрегатная функция работает «над набором значений», имеется в виду, что внутреннее выражение (например, n.age) вычисляется для каждой записи в пределах одной группы агрегации.

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

Рассмотрим следующее выражение RETURN:

SELECT * FROM cypher('graph_name', $$
    MATCH (v:Person)
    RETURN v.name, count(*)
$$) AS (grouping_key agtype, count agtype);
count key
"A" 1
"B" 1
"C" 1
"D" 2
(1 строка)

В нём два возвращаемых выражения: v.name и count(*). Первое не является агрегатным, поэтому оно становится неявным ключом группировки. Второе — агрегатное. Совпавшие записи группируются по значению v.name, а count(*) вычисляется один раз для каждой группы.

Агрегация с DISTINCT

При агрегации с DISTINCT (то есть при вызове aggr(DISTINCT expr)) сначала из списка кандидатных значений удаляются все NULL. Затем из эквивалентных значений сохраняется только одно — дубликаты (по отношению эквивалентности) исключаются.

Оператор DISTINCT используется с агрегатами, чтобы обеспечить уникальность до того, как агрегатная функция начнёт обработку значений.

SELECT *
FROM cypher('graph_name', $$
    MATCH (v:Person)
    RETURN count(DISTINCT v.eyes), count(v.eyes)
$$) AS (distinct_eyes agtype, eyes agtype);
distinct_eyes eyes
2 3
(1 строка)

Неоднозначные операторы группировки

Автоматическое поведение группировки в Cypher — когда пользователь не обязан явно объявлять ключи группировки — может приводить к неоднозначности.

Подготовка данных

SELECT * FROM cypher('graph_name', $$
    CREATE (:L {a: 1, b: 2, c: 3}),
           (:L {a: 2, b: 3, c: 1}),
           (:L {a: 3, b: 1, c: 2})
$$) AS (a agtype);

Некорректный запрос в AGE

AGE устраняет неоднозначность, требуя, чтобы любая переменная, используемая вне агрегатной функции, либо:

  • явно указывалась как ключ группировки в том же предложении WITH или RETURN, либо
  • полностью входила в состав агрегатного выражения.

Запрос

SELECT * FROM cypher('graph_name', $$
    MATCH (x:L)
    RETURN x.a + count(*) + x.b + count(*) + x.c
$$) AS (a agtype);

Результат

ERROR:  "x" must be either part of an explicitly listed key or used inside an aggregate function
LINE 3: RETURN x.a + count(*) + x.b + count(*) + x.c

Корректные запросы в AGE

В AGE любой неагрегатный столбец в предложении WITH или RETURN рассматривается как ключ группировки.

Вариант 1: Объединение неагрегатных термов в одно выражение группировки

SELECT * FROM cypher('graph_name', $$
    MATCH (x:L)
    RETURN (x.a + x.b + x.c) + count(*) + count(*), x.a + x.b + x.c
$$) AS (count agtype, key agtype);

Здесь x.a + x.b + x.c служит ключом группировки. Для формирования составного ключа скобки обязательны.

count key
12 6
(1 строка)

Вариант 2: Явное перечисление каждой переменной в качестве отдельного ключа группировки

SELECT * FROM cypher('graph_name', $$
    MATCH (x:L)
    RETURN x.a + count(*) + x.b + count(*) + x.c, x.a, x.b, x.c
$$) AS (count agtype, a agtype, b agtype, c agtype);

Здесь x.a, x.b и x.c рассматриваются как независимые ключи группировки.

count a b c
8 3 1 2
8 2 3 1
8 1 2 3
(3 строки)

Вершины и рёбра как ключи группировки

Сама вершина или ребро может использоваться в качестве ключа группировки. При этом любые их свойства можно напрямую ссылаться в том же предложении WITH или RETURN без явного объявления.

SELECT * FROM cypher('graph_name', $$
    MATCH (x:L)
    RETURN count(*) + count(*) + x.a + x.b + x.c, x
$$) AS (count agtype, key agtype);

Результат группируется по x. Поскольку вершины идентифицируются по ссылке, их свойства не требуется перечислять для однозначной группировки.

Скрытие нежелательных ключей группировки

Если ключ группировки необходим для агрегации, но не должен присутствовать в итоговом результате, выполните агрегацию в предложении WITH, а в RETURN передайте только нужные значения.

SELECT * FROM cypher('graph_name', $$
    MATCH (x:L)
    WITH count(*) + count(*) + x.a + x.b + x.c AS column, x
    RETURN column
$$) AS (a agtype);
a
8
8
8
(3 строки)