Перейти к основному содержанию
Перейти к основному содержанию

Параметрические агрегатные функции

Некоторые агрегатные функции могут принимать не только столбцы-аргументы (используемые для сжатия), но и набор параметров — констант для инициализации. В синтаксисе для этого используются две пары круглых скобок вместо одной: первая — для параметров, вторая — для аргументов.

histogram

Вычисляет адаптивную гистограмму. Не гарантирует точных результатов.

histogram(число_интервалов)(значения)

Функция использует алгоритм A Streaming Parallel Decision Tree Algorithm. Границы корзин гистограммы корректируются по мере поступления новых данных в функцию. В общем случае ширины корзин могут различаться.

Аргументы

valuesвыражение, возвращающее входные значения.

Параметры

number_of_bins — верхний предел количества корзин в гистограмме. Функция автоматически вычисляет количество корзин. Она пытается достичь указанного количества корзин, но если это не удаётся, использует меньшее количество.

Возвращаемые значения

  • Массив кортежей следующего формата:

    [(lower_1, upper_1, height_1), ... (lower_N, upper_N, height_N)]
    
    • lower — нижняя граница корзины.
    • upper — верхняя граница корзины.
    • height — вычисленная высота корзины.

Пример

SELECT histogram(5)(number + 1)
FROM (
    SELECT *
    FROM system.numbers
    LIMIT 20
)
┌─histogram(5)(plus(number, 1))───────────────────────────────────────────┐
│ [(1,4.5,4),(4.5,8.5,4),(8.5,12.75,4.125),(12.75,17,4.625),(17,20,3.25)] │
└─────────────────────────────────────────────────────────────────────────┘

Вы можете построить гистограмму с помощью функции bar, например:

WITH histogram(5)(rand() % 100) AS hist
SELECT
    arrayJoin(hist).3 AS height,
    bar(height, 0, 6, 5) AS bar
FROM
(
    SELECT *
    FROM system.numbers
    LIMIT 20
)
┌─height─┬─bar───┐
│  2.125 │ █▋    │
│   3.25 │ ██▌   │
│  5.625 │ ████▏ │
│  5.625 │ ████▏ │
│  3.375 │ ██▌   │
└────────┴───────┘

В этом случае следует помнить, что вы не знаете границы интервалов гистограммы.

sequenceMatch

Проверяет, содержит ли последовательность цепочку событий, соответствующую заданному шаблону.

Синтаксис

sequenceMatch(pattern)(timestamp, cond1, cond2, ...)
Примечание

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

Аргументы

  • timestamp — Столбец, содержащий данные времени. Типичные типы данных: Date и DateTime. Вы также можете использовать любой из поддерживаемых типов данных UInt.

  • cond1, cond2 — Условия, описывающие цепочку событий. Тип данных: UInt8. Можно передать до 32 аргументов-условий. Функция учитывает только события, описанные этими условиями. Если последовательность содержит данные, не описанные ни одним условием, функция их пропускает.

Параметры

Возвращаемые значения

  • 1, если шаблон совпал.
  • 0, если шаблон не совпал.

Тип: UInt8.

Синтаксис шаблона

  • (?N) — Соответствует аргументу-условию в позиции N. Условия нумеруются в диапазоне [1, 32]. Например, (?1) соответствует аргументу, переданному параметру cond1.

  • .* — Соответствует любому количеству событий. Для сопоставления этого элемента шаблона не требуются условные аргументы.

  • (?t operator value) — Задаёт время в секундах, которое должно разделять два события. Например, шаблон (?1)(?t>1800)(?2) соответствует событиям, между которыми проходит более 1800 секунд. Между этими событиями может быть произвольное количество любых событий. Можно использовать операторы >=, >, <, <=, ==.

Примеры

Рассмотрим данные в таблице t:

┌─time─┬─number─┐
│    1 │      1 │
│    2 │      3 │
│    3 │      2 │
└──────┴────────┘

Выполните запрос:

SELECT sequenceMatch('(?1)(?2)')(time, number = 1, number = 2) FROM t
┌─sequenceMatch('(?1)(?2)')(time, equals(number, 1), equals(number, 2))─┐
│                                                                     1 │
└───────────────────────────────────────────────────────────────────────┘

Функция нашла цепочку событий, в которой число 2 следует за числом 1. Она пропустила число 3 между ними, потому что оно не задано как событие. Если мы хотим учитывать это число при поиске цепочки событий, приведённой в примере, нужно задать для него условие.

SELECT sequenceMatch('(?1)(?2)')(time, number = 1, number = 2, number = 3) FROM t
┌─sequenceMatch('(?1)(?2)')(time, equals(number, 1), equals(number, 2), equals(number, 3))─┐
│                                                                                        0 │
└──────────────────────────────────────────────────────────────────────────────────────────┘

В этом случае функция не смогла найти цепочку событий, соответствующую шаблону, потому что событие с номером 3 произошло между 1 и 2. Если бы в этом же случае мы проверяли условие для числа 4, последовательность соответствовала бы шаблону.

SELECT sequenceMatch('(?1)(?2)')(time, number = 1, number = 2, number = 4) FROM t
┌─sequenceMatch('(?1)(?2)')(time, equals(number, 1), equals(number, 2), equals(number, 4))─┐
│                                                                                        1 │
└──────────────────────────────────────────────────────────────────────────────────────────┘

См. также

sequenceCount

Подсчитывает количество цепочек событий, соответствующих шаблону. Функция ищет цепочки событий, которые не перекрываются: после сопоставления текущей цепочки она начинает поиск следующей.

Примечание

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

Синтаксис

sequenceCount(pattern)(timestamp, cond1, cond2, ...)

Аргументы

  • timestamp — Столбец, содержащий временные данные. Типичные типы данных: Date и DateTime. Также можно использовать любой из поддерживаемых беззнаковых целочисленных типов UInt.

  • cond1, cond2 — Условия, описывающие цепочку событий. Тип данных: UInt8. Можно передать до 32 аргументов-условий. Функция учитывает только события, описанные этими условиями. Если последовательность содержит данные, которые не описаны ни в одном условии, функция их пропускает.

Параметры

Возвращаемые значения

  • Количество непересекающихся цепочек событий, удовлетворяющих шаблону.

Тип: UInt64.

Пример

Рассмотрим данные в таблице t:

┌─time─┬─number─┐
│    1 │      1 │
│    2 │      3 │
│    3 │      2 │
│    4 │      1 │
│    5 │      3 │
│    6 │      2 │
└──────┴────────┘

Посчитайте, сколько раз число 2 встречается после числа 1 с любым количеством других чисел между ними:

SELECT sequenceCount('(?1).*(?2)')(time, number = 1, number = 2) FROM t
┌─sequenceCount('(?1).*(?2)')(time, equals(number, 1), equals(number, 2))─┐
│                                                                       2 │
└─────────────────────────────────────────────────────────────────────────┘

sequenceMatchEvents

Возвращает временные метки событий для наиболее длинных цепочек, соответствующих шаблону.

Примечание

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

Синтаксис

sequenceMatchEvents(pattern)(timestamp, cond1, cond2, ...)

Аргументы

  • timestamp — Столбец, содержащий данные о времени. Типичные типы данных: Date и DateTime. Также можно использовать любой из поддерживаемых типов данных UInt.

  • cond1, cond2 — Условия, описывающие цепочку событий. Тип данных: UInt8. Можно передать до 32 аргументов-условий. Функция учитывает только события, описанные этими условиями. Если последовательность содержит данные, которые не описаны ни одним условием, функция их пропускает.

Параметры

Возвращаемые значения

  • Массив меток времени для аргументов-условий (?N), удовлетворяющих шаблону, из цепочки событий. Позиция в массиве соответствует позиции аргумента-условия в шаблоне.

Тип: Array.

Пример

Рассмотрим данные в таблице t:

┌─time─┬─number─┐
│    1 │      1 │
│    2 │      3 │
│    3 │      2 │
│    4 │      1 │
│    5 │      3 │
│    6 │      2 │
└──────┴────────┘

Возвращает временные метки событий для самой длинной цепочки

SELECT sequenceMatchEvents('(?1).*(?2).*(?1)(?3)')(time, number = 1, number = 2, number = 4) FROM t
┌─sequenceMatchEvents('(?1).*(?2).*(?1)(?3)')(time, equals(number, 1), equals(number, 2), equals(number, 4))─┐
│ [1,3,4]                                                                                                    │
└────────────────────────────────────────────────────────────────────────────────────────────────────────────┘

См. также

windowFunnel

Ищет цепочки событий в скользящем временном окне и вычисляет максимальное число событий из цепочки, произошедших в этом окне.

Функция работает по следующему алгоритму:

  • Функция ищет данные, которые соответствуют первому условию в цепочке, и устанавливает счётчик событий равным 1. В этот момент начинается скользящее окно.

  • Если события из цепочки происходят последовательно в пределах окна, счётчик увеличивается. Если последовательность событий нарушается, счётчик не увеличивается.

  • Если в данных есть несколько цепочек событий с разной степенью завершённости, функция выводит только размер самой длинной цепочки.

Синтаксис

windowFunnel(window, [mode, [mode, ... ]])(timestamp, cond1, cond2, ..., condN)

Аргументы

  • timestamp — имя столбца, содержащего метку времени. Поддерживаемые типы данных: Date, DateTime и другие беззнаковые целочисленные типы (обратите внимание, что хотя timestamp поддерживает тип UInt64, его значение не может превышать максимальное значение для Int64, равное 2^63 - 1).
  • cond — условия или данные, описывающие цепочку событий. UInt8.

Параметры

  • window — длина скользящего окна, временной интервал между первым и последним условием. Единица измерения window зависит от самого timestamp и может различаться. Определяется выражением timestamp of cond1 <= timestamp of cond2 <= ... <= timestamp of condN <= timestamp of cond1 + window.
  • mode — необязательный аргумент. Можно задать один или несколько режимов.
    • 'strict_deduplication' — если одно и то же условие выполняется для последовательности событий, то такое повторяющееся событие прерывает дальнейшую обработку. Примечание: может работать неожиданно, если для одного и того же события выполняется несколько условий.
    • 'strict_order' — не допускать вклинивания других событий. Например, в случае A->B->D->C поиск A->B->C останавливается на D, и максимальный уровень события равен 2.
    • 'strict_increase' — применять условия только к событиям со строго возрастающими метками времени.
    • 'strict_once' — учитывать каждое событие в цепочке только один раз, даже если оно удовлетворяет условию несколько раз.

Возвращаемое значение

Максимальное количество последовательных сработавших условий из цепочки в пределах скользящего временного окна. Анализируются все цепочки в выборке.

Тип: Integer.

Пример

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

Задайте следующую цепочку событий:

  1. Пользователь вошёл в свой аккаунт в магазине (eventID = 1003).
  2. Пользователь ищет телефон (eventID = 1007, product = 'phone').
  3. Пользователь оформил заказ (eventID = 1009).
  4. Пользователь оформил повторный заказ (eventID = 1010).

Входная таблица:

┌─event_date─┬─user_id─┬───────────timestamp─┬─eventID─┬─product─┐
│ 2019-01-28 │       1 │ 2019-01-29 10:00:00 │    1003 │ phone   │
└────────────┴─────────┴─────────────────────┴─────────┴─────────┘
┌─event_date─┬─user_id─┬───────────timestamp─┬─eventID─┬─product─┐
│ 2019-01-31 │       1 │ 2019-01-31 09:00:00 │    1007 │ phone   │
└────────────┴─────────┴─────────────────────┴─────────┴─────────┘
┌─event_date─┬─user_id─┬───────────timestamp─┬─eventID─┬─product─┐
│ 2019-01-30 │       1 │ 2019-01-30 08:00:00 │    1009 │ phone   │
└────────────┴─────────┴─────────────────────┴─────────┴─────────┘
┌─event_date─┬─user_id─┬───────────timestamp─┬─eventID─┬─product─┐
│ 2019-02-01 │       1 │ 2019-02-01 08:00:00 │    1010 │ phone   │
└────────────┴─────────┴─────────────────────┴─────────┴─────────┘

Узнайте, как далеко пользователь user_id продвинулся по цепочке за период январь–февраль 2019 года.

Запрос:

SELECT
    level,
    count() AS c
FROM
(
    SELECT
        user_id,
        windowFunnel(6048000000000000)(timestamp, eventID = 1003, eventID = 1009, eventID = 1007, eventID = 1010) AS level
    FROM trend
    WHERE (event_date >= '2019-01-01') AND (event_date <= '2019-02-02')
    GROUP BY user_id
)
GROUP BY level
ORDER BY level ASC;

Результат:

┌─level─┬─c─┐
│     4 │ 1 │
└───────┴───┘

retention

Функция принимает в качестве аргументов набор из 1–32 условий типа UInt8, которые указывают, было ли выполнено определённое условие для события. Любое условие может быть указано в качестве аргумента (как в WHERE).

Условия, кроме первого, применяются попарно: результат второго будет true, если первое и второе истинны; третьего — если первое и третье истинны и т. д.

Синтаксис

retention(cond1, cond2, ..., cond32);

Аргументы

  • cond — выражение, которое возвращает результат типа UInt8 (1 или 0).

Возвращаемое значение

Массив значений 1 или 0.

  • 1 — условие было выполнено для события.
  • 0 — условие не было выполнено для события.

Тип: UInt8.

Пример

Рассмотрим пример вычисления функции retention для определения трафика сайта.

1. Создайте таблицу для иллюстрации.

CREATE TABLE retention_test(date Date, uid Int32) ENGINE = Memory;

INSERT INTO retention_test SELECT '2020-01-01', number FROM numbers(5);
INSERT INTO retention_test SELECT '2020-01-02', number FROM numbers(10);
INSERT INTO retention_test SELECT '2020-01-03', number FROM numbers(15);

Входная таблица:

Запрос:

SELECT * FROM retention_test

Результат:

┌───────date─┬─uid─┐
│ 2020-01-01 │   0 │
│ 2020-01-01 │   1 │
│ 2020-01-01 │   2 │
│ 2020-01-01 │   3 │
│ 2020-01-01 │   4 │
└────────────┴─────┘
┌───────date─┬─uid─┐
│ 2020-01-02 │   0 │
│ 2020-01-02 │   1 │
│ 2020-01-02 │   2 │
│ 2020-01-02 │   3 │
│ 2020-01-02 │   4 │
│ 2020-01-02 │   5 │
│ 2020-01-02 │   6 │
│ 2020-01-02 │   7 │
│ 2020-01-02 │   8 │
│ 2020-01-02 │   9 │
└────────────┴─────┘
┌───────date─┬─uid─┐
│ 2020-01-03 │   0 │
│ 2020-01-03 │   1 │
│ 2020-01-03 │   2 │
│ 2020-01-03 │   3 │
│ 2020-01-03 │   4 │
│ 2020-01-03 │   5 │
│ 2020-01-03 │   6 │
│ 2020-01-03 │   7 │
│ 2020-01-03 │   8 │
│ 2020-01-03 │   9 │
│ 2020-01-03 │  10 │
│ 2020-01-03 │  11 │
│ 2020-01-03 │  12 │
│ 2020-01-03 │  13 │
│ 2020-01-03 │  14 │
└────────────┴─────┘

2. Сгруппируйте пользователей по уникальному идентификатору uid, используя функцию retention.

Запрос:

SELECT
    uid,
    retention(date = '2020-01-01', date = '2020-01-02', date = '2020-01-03') AS r
FROM retention_test
WHERE date IN ('2020-01-01', '2020-01-02', '2020-01-03')
GROUP BY uid
ORDER BY uid ASC

Результат:

┌─uid─┬─r───────┐
│   0 │ [1,1,1] │
│   1 │ [1,1,1] │
│   2 │ [1,1,1] │
│   3 │ [1,1,1] │
│   4 │ [1,1,1] │
│   5 │ [0,0,0] │
│   6 │ [0,0,0] │
│   7 │ [0,0,0] │
│   8 │ [0,0,0] │
│   9 │ [0,0,0] │
│  10 │ [0,0,0] │
│  11 │ [0,0,0] │
│  12 │ [0,0,0] │
│  13 │ [0,0,0] │
│  14 │ [0,0,0] │
└─────┴─────────┘

3. Рассчитайте общее количество посещений сайта за каждый день.

Запрос:

SELECT
    sum(r[1]) AS r1,
    sum(r[2]) AS r2,
    sum(r[3]) AS r3
FROM
(
    SELECT
        uid,
        retention(date = '2020-01-01', date = '2020-01-02', date = '2020-01-03') AS r
    FROM retention_test
    WHERE date IN ('2020-01-01', '2020-01-02', '2020-01-03')
    GROUP BY uid
)

Результат:

┌─r1─┬─r2─┬─r3─┐
│  5 │  5 │  5 │
└────┴────┴────┘

Где:

  • r1 — количество уникальных посетителей, которые посетили сайт за 2020-01-01 (условие cond1).
  • r2 — количество уникальных посетителей, которые посетили сайт в течение определённого периода времени между 2020-01-01 и 2020-01-02 (условия cond1 и cond2).
  • r3 — количество уникальных посетителей, которые посетили сайт в течение определённого периода времени в даты 2020-01-01 и 2020-01-03 (условия cond1 и cond3).

uniqUpTo(N)(x)

Вычисляет количество различных значений аргумента до заданного предела N. Если количество различных значений аргумента больше N, функция возвращает N + 1, в противном случае вычисляет точное значение.

Рекомендуется использовать с небольшими N, до 10. Максимальное значение N — 100.

В состоянии агрегатной функции эта функция использует объём памяти, равный 1 + N * размер одного значения в байтах. При работе со строками она сохраняет некриптографический хэш размером 8 байт; для строк вычисление является приблизительным.

Например, предположим, что у вас есть таблица, в которой регистрируется каждый поисковый запрос, сделанный пользователями на вашем сайте. Каждая строка в таблице представляет один поисковый запрос, со столбцами для идентификатора пользователя, текста поискового запроса и временной метки запроса. Вы можете использовать uniqUpTo, чтобы сформировать отчёт, который показывает только те ключевые слова, по которым было как минимум 5 уникальных пользователей.

SELECT SearchPhrase
FROM SearchLog
GROUP BY SearchPhrase
HAVING uniqUpTo(4)(UserID) >= 5

uniqUpTo(4)(UserID) вычисляет количество уникальных значений UserID для каждого SearchPhrase, но считает только до 4 уникальных значений. Если для какого-либо SearchPhrase существует более 4 уникальных значений UserID, функция возвращает 5 (4 + 1). Затем условие HAVING отфильтровывает значения SearchPhrase, для которых количество уникальных значений UserID меньше 5. В результате вы получите список поисковых запросов, которые использовались как минимум 5 уникальными пользователями.

sumMapFiltered

Эта функция ведёт себя так же, как sumMap, за исключением того, что она также принимает в качестве параметра массив ключей для фильтрации. Это может быть особенно полезно при работе с высокой кардинальностью ключей.

Синтаксис

sumMapFiltered(keys_to_keep)(keys, values)

Параметры

  • keys_to_keep: Array — массив ключей, по которым выполняется фильтрация.
  • keys: Array — массив ключей.
  • values: Array — массив значений.

Возвращаемое значение

  • Возвращает кортеж из двух массивов: ключи в отсортированном порядке и значения, суммированные по соответствующим ключам.

Пример

Запрос:

CREATE TABLE sum_map
(
    `date` Date,
    `timeslot` DateTime,
    `statusMap` Nested(status UInt16, requests UInt64)
)
ENGINE = Log

INSERT INTO sum_map VALUES
    ('2000-01-01', '2000-01-01 00:00:00', [1, 2, 3], [10, 10, 10]),
    ('2000-01-01', '2000-01-01 00:00:00', [3, 4, 5], [10, 10, 10]),
    ('2000-01-01', '2000-01-01 00:01:00', [4, 5, 6], [10, 10, 10]),
    ('2000-01-01', '2000-01-01 00:01:00', [6, 7, 8], [10, 10, 10]);
SELECT sumMapFiltered([1, 4, 8])(statusMap.status, statusMap.requests) FROM sum_map;

Результат:

   ┌─sumMapFiltered([1, 4, 8])(statusMap.status, statusMap.requests)─┐
1. │ ([1,4,8],[10,20,10])                                            │
   └─────────────────────────────────────────────────────────────────┘

sumMapFilteredWithOverflow

Эта функция ведёт себя так же, как sumMap, за исключением того, что она дополнительно принимает в качестве параметра массив ключей для фильтрации. Это может быть особенно полезно при работе с высокой кардинальностью ключей. Она отличается от функции sumMapFiltered тем, что выполняет суммирование с переполнением, то есть возвращает для результата суммирования тот же тип данных, что и тип данных аргумента.

Синтаксис

sumMapFilteredWithOverflow(keys_to_keep)(keys, values)

Параметры

  • keys_to_keep: Array ключей для фильтрации.
  • keys: Array ключей.
  • values: Array значений.

Возвращаемое значение

  • Возвращает кортеж из двух массивов: ключи в отсортированном порядке и значения, суммированные для соответствующих ключей.

Пример

В этом примере мы создаём таблицу sum_map, вставляем в неё некоторые данные, а затем используем sumMapFilteredWithOverflow и sumMapFiltered, а также функцию toTypeName для сравнения результата. Поскольку requests имел тип UInt8 в созданной таблице, sumMapFiltered привела тип суммируемых значений к UInt64, чтобы избежать переполнения, тогда как sumMapFilteredWithOverflow сохранила тип UInt8, который недостаточно велик для хранения результата, то есть произошло переполнение.

Запрос:

CREATE TABLE sum_map
(
    `date` Date,
    `timeslot` DateTime,
    `statusMap` Nested(status UInt8, requests UInt8)
)
ENGINE = Log

INSERT INTO sum_map VALUES
    ('2000-01-01', '2000-01-01 00:00:00', [1, 2, 3], [10, 10, 10]),
    ('2000-01-01', '2000-01-01 00:00:00', [3, 4, 5], [10, 10, 10]),
    ('2000-01-01', '2000-01-01 00:01:00', [4, 5, 6], [10, 10, 10]),
    ('2000-01-01', '2000-01-01 00:01:00', [6, 7, 8], [10, 10, 10]);
SELECT sumMapFilteredWithOverflow([1, 4, 8])(statusMap.status, statusMap.requests) as summap_overflow, toTypeName(summap_overflow) FROM sum_map;
SELECT sumMapFiltered([1, 4, 8])(statusMap.status, statusMap.requests) as summap, toTypeName(summap) FROM sum_map;

Результат:

   ┌─sum──────────────────┬─toTypeName(sum)───────────────────┐
1. │ ([1,4,8],[10,20,10]) │ Tuple(Array(UInt8), Array(UInt8)) │
   └──────────────────────┴───────────────────────────────────┘
   ┌─summap───────────────┬─toTypeName(summap)─────────────────┐
1. │ ([1,4,8],[10,20,10]) │ Tuple(Array(UInt8), Array(UInt64)) │
   └──────────────────────┴────────────────────────────────────┘

sequenceNextNode

Возвращает значение следующего события, которое соответствует цепочке событий.

Экспериментальная функция, включается с помощью SET allow_experimental_funnel_functions = 1.

Синтаксис

sequenceNextNode(direction, base)(timestamp, event_column, base_condition, event1, event2, event3, ...)

Параметры

  • direction — Используется для указания направления поиска.

    • forward — Движение вперёд.
    • backward — Движение назад.
  • base — Используется для задания опорной точки.

    • head — Устанавливает опорную точку на первое событие.
    • tail — Устанавливает опорную точку на последнее событие.
    • first_match — Устанавливает опорную точку на первое совпавшее event1.
    • last_match — Устанавливает опорную точку на последнее совпавшее event1.

Аргументы

  • timestamp — Имя столбца, содержащего метку времени. Поддерживаемые типы данных: Date, DateTime и другие беззнаковые целочисленные типы.
  • event_column — Имя столбца, содержащего значение следующего события, которое должно быть возвращено. Поддерживаемые типы данных: String и Nullable(String).
  • base_condition — Условие, которому должна удовлетворять опорная точка.
  • event1, event2, ... — Условия, описывающие цепочку событий. Тип данных: UInt8.

Возвращаемые значения

  • event_column[next_index] — Если шаблон совпал и следующее значение существует.
  • NULL — Если шаблон не совпал или следующее значение не существует.

Тип: Nullable(String).

Пример

Функцию можно использовать, когда события имеют вид A->B->C->D->E и нужно узнать событие, следующее за B->C, то есть D.

Пример запроса, который ищет событие, следующее за A->B:

CREATE TABLE test_flow (
    dt DateTime,
    id int,
    page String)
ENGINE = MergeTree()
PARTITION BY toYYYYMMDD(dt)
ORDER BY id;

INSERT INTO test_flow VALUES (1, 1, 'A') (2, 1, 'B') (3, 1, 'C') (4, 1, 'D') (5, 1, 'E');

SELECT id, sequenceNextNode('forward', 'head')(dt, page, page = 'A', page = 'A', page = 'B') as next_flow FROM test_flow GROUP BY id;

Результат:

┌─id─┬─next_flow─┐
│  1 │ C         │
└────┴───────────┘

Поведение forward и head

ALTER TABLE test_flow DELETE WHERE 1 = 1 settings mutations_sync = 1;

INSERT INTO test_flow VALUES (1, 1, 'Home') (2, 1, 'Gift') (3, 1, 'Exit');
INSERT INTO test_flow VALUES (1, 2, 'Home') (2, 2, 'Home') (3, 2, 'Gift') (4, 2, 'Basket');
INSERT INTO test_flow VALUES (1, 3, 'Gift') (2, 3, 'Home') (3, 3, 'Gift') (4, 3, 'Basket');
SELECT id, sequenceNextNode('forward', 'head')(dt, page, page = 'Home', page = 'Home', page = 'Gift') FROM test_flow GROUP BY id;

                  dt   id   page
 1970-01-01 09:00:01    1   Home // Исходная точка, совпадает с Home
 1970-01-01 09:00:02    1   Gift // Совпадает с Gift
 1970-01-01 09:00:03    1   Exit // Результат

 1970-01-01 09:00:01    2   Home // Исходная точка, совпадает с Home
 1970-01-01 09:00:02    2   Home // Не совпадает с Gift
 1970-01-01 09:00:03    2   Gift
 1970-01-01 09:00:04    2   Basket

1970-01-01 09:00:01 3 Gift // Опорная точка, не сопоставлена с Home 1970-01-01 09:00:02 3 Home 1970-01-01 09:00:03 3 Gift 1970-01-01 09:00:04 3 Basket


**Поведение для `backward` и `tail`**

```sql
SELECT id, sequenceNextNode('backward', 'tail')(dt, page, page = 'Basket', page = 'Basket', page = 'Gift') FROM test_flow GROUP BY id;

                 dt   id   page
1970-01-01 09:00:01    1   Home
1970-01-01 09:00:02    1   Gift
1970-01-01 09:00:03    1   Exit // Базовая точка, не соответствует Basket

1970-01-01 09:00:01    2   Home
1970-01-01 09:00:02    2   Home // Результат
1970-01-01 09:00:03    2   Gift // Соответствует Gift
1970-01-01 09:00:04    2   Basket // Базовая точка, соответствует Basket

1970-01-01 09:00:01    3   Gift
1970-01-01 09:00:02    3   Home // Результат
1970-01-01 09:00:03    3   Gift // Базовая точка, соответствует Gift
1970-01-01 09:00:04    3   Basket // Базовая точка, соответствует Basket

Поведение режимов forward и first_match

SELECT id, sequenceNextNode('forward', 'first_match')(dt, page, page = 'Gift', page = 'Gift') FROM test_flow GROUP BY id;

                 dt   id   page
1970-01-01 09:00:01    1   Home
1970-01-01 09:00:02    1   Gift // Базовая точка
1970-01-01 09:00:03    1   Exit // Результат

1970-01-01 09:00:01    2   Home
1970-01-01 09:00:02    2   Home
1970-01-01 09:00:03    2   Gift // Базовая точка
1970-01-01 09:00:04    2   Basket  // Результат

1970-01-01 09:00:01    3   Gift // Базовая точка
1970-01-01 09:00:02    3   Home // Результат
1970-01-01 09:00:03    3   Gift
1970-01-01 09:00:04    3   Basket
SELECT id, sequenceNextNode('forward', 'first_match')(dt, page, page = 'Gift', page = 'Gift', page = 'Home') FROM test_flow GROUP BY id;

                 dt   id   page
1970-01-01 09:00:01    1   Home
1970-01-01 09:00:02    1   Gift // Базовая точка
1970-01-01 09:00:03    1   Exit // Не совпадает с Home

1970-01-01 09:00:01    2   Home
1970-01-01 09:00:02    2   Home
1970-01-01 09:00:03    2   Gift // Базовая точка
1970-01-01 09:00:04    2   Basket // Не совпадает с Home

1970-01-01 09:00:01    3   Gift // Базовая точка
1970-01-01 09:00:02    3   Home // Совпадает с Home
1970-01-01 09:00:03    3   Gift // Результат
1970-01-01 09:00:04    3   Basket

Поведение backward и last_match

SELECT id, sequenceNextNode('backward', 'last_match')(dt, page, page = 'Gift', page = 'Gift') FROM test_flow GROUP BY id;

dt id page 1970-01-01 09:00:01 1 Home // Результат 1970-01-01 09:00:02 1 Gift // Базовая страница 1970-01-01 09:00:03 1 Exit

1970-01-01 09:00:01 2 Home 1970-01-01 09:00:02 2 Home // Результат 1970-01-01 09:00:03 2 Gift // Базовая страница 1970-01-01 09:00:04 2 Basket

1970-01-01 09:00:01 3 Gift 1970-01-01 09:00:02 3 Home // Результат 1970-01-01 09:00:03 3 Gift // Базовая страница 1970-01-01 09:00:04 3 Basket


```sql
SELECT id, sequenceNextNode('backward', 'last_match')(dt, page, page = 'Gift', page = 'Gift', page = 'Home') FROM test_flow GROUP BY id;

                 dt   id   page
1970-01-01 09:00:01    1   Home // Совпадает с Home, результат — null
1970-01-01 09:00:02    1   Gift // Базовая точка
1970-01-01 09:00:03    1   Exit

1970-01-01 09:00:01    2   Home // Результат
1970-01-01 09:00:02    2   Home // Совпадает с Home
1970-01-01 09:00:03    2   Gift // Базовая точка
1970-01-01 09:00:04    2   Basket

1970-01-01 09:00:01    3   Gift // Результат
1970-01-01 09:00:02    3   Home // Совпадает с Home
1970-01-01 09:00:03    3   Gift // Базовая точка
1970-01-01 09:00:04    3   Basket

Поведение параметра base_condition

CREATE TABLE test_flow_basecond
(
    `dt` DateTime,
    `id` int,
    `page` String,
    `ref` String
)
ENGINE = MergeTree
PARTITION BY toYYYYMMDD(dt)
ORDER BY id;

INSERT INTO test_flow_basecond VALUES (1, 1, 'A', 'ref4') (2, 1, 'A', 'ref3') (3, 1, 'B', 'ref2') (4, 1, 'B', 'ref1');
SELECT id, sequenceNextNode('forward', 'head')(dt, page, ref = 'ref1', page = 'A') FROM test_flow_basecond GROUP BY id;

                  dt   id   page   ref
 1970-01-01 09:00:01    1   A      ref4 // Начальная строка не может быть базовой точкой, поскольку значение столбца ref начальной строки не соответствует 'ref1'.
 1970-01-01 09:00:02    1   A      ref3
 1970-01-01 09:00:03    1   B      ref2
 1970-01-01 09:00:04    1   B      ref1
SELECT id, sequenceNextNode('backward', 'tail')(dt, page, ref = 'ref4', page = 'B') FROM test_flow_basecond GROUP BY id;

                  dt   id   page   ref
 1970-01-01 09:00:01    1   A      ref4
 1970-01-01 09:00:02    1   A      ref3
 1970-01-01 09:00:03    1   B      ref2
 1970-01-01 09:00:04    1   B      ref1 // Хвост не может быть базовой точкой, поскольку значение столбца ref в хвосте не соответствует 'ref4'.
SELECT id, sequenceNextNode('forward', 'first_match')(dt, page, ref = 'ref3', page = 'A') FROM test_flow_basecond GROUP BY id;

dt id page ref 1970-01-01 09:00:01 1 A ref4 // Эта строка не может быть опорной точкой, потому что значение в столбце ref не совпадает с 'ref3'. 1970-01-01 09:00:02 1 A ref3 // Опорная точка 1970-01-01 09:00:03 1 B ref2 // Результат 1970-01-01 09:00:04 1 B ref1


```sql
SELECT id, sequenceNextNode('backward', 'last_match')(dt, page, ref = 'ref2', page = 'B') FROM test_flow_basecond GROUP BY id;

                  dt   id   page   ref
 1970-01-01 09:00:01    1   A      ref4
 1970-01-01 09:00:02    1   A      ref3 // Результат
 1970-01-01 09:00:03    1   B      ref2 // Базовая точка
 1970-01-01 09:00:04    1   B      ref1 // Эта строка не может быть базовой точкой, поскольку значение столбца ref не совпадает с 'ref2'.