База знаний GetMyBot

Страницы, итоги и совместное изменение записей

Три упражнения: показать следующую страницу каталога, посчитать итог по всей доступной коллекции и занять последнее место без частично сохранённой заявки.

На этой странице

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

ЗадачаДействие
Показывать каталог небольшими порциямиПрочитать страницу коллекции (record_page)
Посчитать по всем подходящим записямИтоги всей коллекции (record_aggregate)
Проверить условия и сохранить несколько изменений вместеАтомарная транзакция коллекций (collection_transaction)

Для упражнений создайте учебные коллекции. В рабочем проекте сначала выполните проверку черновика: обычный запуск транзакции действительно меняет данные.

Урок 1. Показать следующую страницу каталога

Создайте коллекцию classes с текстовыми полями code, title, state и числовым price. Добавьте пять доступных занятий с разными кодами и ценами. Укажите state=open у всех пяти.

  1. Добавьте Прочитать страницу коллекции. Выберите classes, условие state равно open, сортировку по price по возрастанию и размер страницы 2.
  2. Сохраните результат как catalog_page. На первом запросе курсор оставьте пустым.
  3. Сохраните реакцию и откройте её снова. Для кнопки продолжения нужен ID сохранённой реакции.
  4. Нажмите Добавить кнопку следующей страницы. Помощник добавит в черновик сообщение со строками текущей страницы и кнопкой «Далее». При необходимости оформите вывод catalog_page.rows, затем сохраните изменения.
  5. Проверьте переход: помощник передаёт курсор следующей страницы, сохраняя те же коллекцию, фильтры, сортировку, размер страницы и выбранные поля.

Нажатие «Далее» снова запускает эту реакцию, включая остальные её действия. Убедитесь, что они подходят для повторного просмотра каталога. Помощник доступен для страницы в основном списке действий реакции; внутри цикла или блока навигация настраивается вручную.

Ожидается три страницы: две строки, ещё две и последняя строка. На последней странице has_more=false, а next_cursor пустой. Кнопки следующей страницы там быть не должно. Повторная отправка пустого курсора начнёт каталог сначала.

Для кнопки используется служебный массив catalog_page.navigation: пока есть продолжение, в нём один элемент, а в конце он пустой. Помощник настраивает динамическую кнопку по этому массиву и передачу курсора через Carry. Поэтому при пустом массиве кнопка больше не создаётся. Как работают динамические кнопки.

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

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

Курсор действует один час от начала просмотра. Изменение схемы коллекции или правил хранения также делает старый курсор непригодным. При такой ошибке предложите пользователю открыть каталог заново; не подменяйте продолжение первой страницей без объяснения.

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

Что означает полнота страницы

count показывает число строк на этой странице. Это не число всех занятий в коллекции.

has_more=false означает, что после этой страницы больше нет строк. Оно не означает, что сама страница содержит всю коллекцию. Особенно это важно на второй и последующих страницах: последние две строки нельзя использовать как «все занятия» для общего итога.

Если передаёте страницу в обработку списка, используйте весь источник catalog_page. Выбор только catalog_page.rows отбрасывает служебную информацию о полноте. Как работают результаты списков.

Как настроить фильтры и порядок

Выбирайте поля, объявленные в коллекции. Тип поля определяет доступные условия. Все добавленные условия должны выполняться одновременно.

УсловиеГде работаетПример
Равно / не равно (eq, neq)Текст, число, логическое значение, датаstate равно open
Больше / не меньше / меньше / не больше (gt, gte, lt, lte)Число, датаprice не меньше 1000
Содержит / начинается с (contains, prefix)Текст, с учётом регистраname начинается с Акварель
Пусто / заполнено (empty, not_empty)Любое полеphone пусто

«Пусто» включает отсутствующее поле, null и пустую строку. Для этого условия значение не задаётся. Условия «равно» и «не равно» пропускают отсутствующие поля и null; для их поиска выбирайте «пусто».

Для периода укажите начало включительно и конец без включения. Например, весь октябрь по UTC: дата не меньше 2026-10-01T00:00:00Z и меньше 2026-11-01T00:00:00Z. Всегда указывайте часовой пояс через Z или смещение вроде +03:00. Значение условия может поступать из переменной; коллекция и имена полей выбираются явно.

Даты сравниваются как моменты времени: 12:00:00+03:00 и 09:00:00Z в один день равны. Точность сравнения достигает наносекунды, девяти знаков дробной части секунды. Лишние знаки отбрасываются без округления. Некорректное время вроде 24:00:00 возвращает ошибку.

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

При необходимости выберите только нужные поля результата. ID исходной записи сохранится. Так проще уложиться в ограничение размера страницы. Если подходящая запись содержит некорректное число или дату для выбранного условия, действие вернёт ошибку. Исправьте данные или условие; частичная страница не выдаётся за успешный результат.

Как проверить чтение прямо в редакторе

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

  1. Если в условии стоит переменная, например {{ctx.customer}}, задайте ниже конкретный пример, например Анна. Без образца запрос не отправится. Для намеренно пустого значения нажмите Использовать пустую строку.
  2. Для первой страницы оставьте курсор проверки пустым. Чтобы проверить продолжение, используйте курсор из ответа. Этот ввод не меняет шаблон курсора в действии.
  3. Нажмите Прочитать без изменений и проверьте строки и признаки полноты. Введённые образцы не заменяют переменные в сохранённых настройках.

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

Урок 2. Посчитать выручку по всем подходящим заказам

Создайте коллекцию orders с текстовыми полями category, state и числовым amount:

categorystateamount
watercolorpaid1500
watercolorpaid1800
claypaid2000
claycancelled900

Добавьте Итоги всей коллекции:

НастройкаЗначение
Коллекцияorders
Условиеstate равно paid
Группировать поcategory
Метрика количестваcount, имя order_count
Метрика суммыsum по amount, имя revenue
Куда сохранитьsales

Ожидаются две строки: watercolor с количеством 2 и суммой 3300, clay с количеством 1 и суммой 2000. Отменённый заказ не учитывается. Уберите группировку, чтобы получить одну строку: order_count=3, revenue=5300.

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

В результате с группировкой служебные count и total равны 2: это число групп. input_count и scanned равны 3: столько заказов прошло фильтр и вошло в расчёт. Метрика order_count внутри каждой строки показывает число заказов в этой категории.

Если подходящих заказов нет, без группировки вернётся одна строка с order_count=0 и revenue=0. При добавлении группировки строк не будет. Для проверки наличия заказов используйте input_count или метрику количества; сам факт одной строки общего итога не означает, что заказы найдены.

Какую метрику выбрать

МетрикаЧто считает
countЧисло записей в группе
sumСумму числовых значений поля
avgСреднее числовых значений поля
min, maxНаименьшее и наибольшее число
count_distinctЧисло разных значений поля, кроме null

Пропущенное поле и null не входят в расчёт по этому полю. count продолжает учитывать саму запись. Если числовых значений нет, сумма равна 0, а среднее, минимум и максимум равны null. Некорректный текст вместо числа приводит к ошибке расчёта. Для count_distinct подходят текст, числа и логические значения; число 1 и текст "1" считаются разными значениями. Пустая строка тоже учитывается как отдельное значение.

Дробные суммы считаются точно. Среднее округляется до 12 знаков после запятой. Попробуйте добавить avg по amount с именем average: без группировки ожидается 1766.666666666667. Для среднего по всем заказам выполняйте расчёт без группировки, так каждый заказ имеет одинаковый вес.

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

Для отчёта за период добавьте условия по своему полю даты. Например, нижнюю границу включите, а верхнюю исключите: так соседние периоды не посчитают граничную запись дважды. Поле должно иметь тип даты и времени.

Проверка различия: загрузите только два оплаченных заказа и отдельно посчитайте их действием Группы и итоги (list_aggregate). Этот результат относится только к загруженному списку. Для общего отчёта используйте record_aggregate.

Если запрос превысил лимит времени, числа групп или объёма результата, действие возвращает ошибку. Частичная сумма не выдаётся как итог по всей коллекции. Сузьте период или число групп и повторите запрос.

Урок 3. Занять последнее место и создать заявку вместе

Пусть коллекция seats содержит одну учебную запись: code=wc-saturday, state=open. В bookings есть текстовые поля seat_code, guest_id, state.

Нужно одновременно закрыть место и создать заявку. Если выполнить два обычных действия подряд, ошибка между ними может оставить только первое изменение. Добавьте действие Атомарная транзакция коллекций с тремя операциями:

  1. Проверить запись (assert): в seats по ID учебной строки ожидается state=open.
  2. Изменить при условии (update_if): у той же строки ожидается state=open, новое значение state=booked.
  3. Создать (create): в bookings записать seat_code=wc-saturday, guest_id={{user_id}}, state=confirmed.

Сохраните результат как booking_result. Настройте исходы:

ИсходОтвет
committed«Место забронировано»
conflict«Это место уже заняли. Выберите другое»
error«Не получилось оформить заявку. Повторите позже»

При успехе оба изменения сохраняются. Если любая проверка не прошла, результат conflict, и все изменения этой транзакции откатываются. Текущие значения чужой или изменившейся записи в результате конфликта не раскрываются. Ошибка данных или БД – отдельный исход error.

Конфликт завершает цепочку, даже если автор не выбрал реакцию для conflict. Сообщение об успехе ниже по цепочке не должно выполняться. Для понятного ответа пользователю настройте все три исхода. Правила исходов.

Проверьте конкурентные запуски и повторы

  • Запустите бронирование для двух учебных пользователей почти одновременно. Должна появиться одна заявка; второй запуск получает конфликт.
  • Для отдельной проверки верните место в open и временно добавьте в конец транзакции ещё одну операцию assert: у этой строки по-прежнему ожидается state=open. Предыдущее изменение уже поставило booked, поэтому последняя проверка даст конфликт. После отката место должно остаться open, а новая заявка не должна появиться. Затем уберите учебную последнюю проверку.
  • Отличайте новый запрос пользователя от повторной обработки того же события после сбоя. Для той же операции backend сохраняет результат вместе с изменениями и возвращает его при повторе, не создавая заявку ещё раз. Новый запрос заново проверяет состояние места.

При вызове через API сохраняйте один ключ операции для всех повторных попыток того же запроса. Изменённые параметры с прежним ключом отклоняются. Для нового самостоятельного запроса нужен новый ключ; один постоянный ключ на все бронирования не подходит.

В REST и MCP это поле invocation_id. Ключ относится к конкретному боту и пользователю API. Смена токена того же пользователя сохраняет эту привязку; вызов от другого пользователя считается отдельной операцией, даже с тем же ключом.

Сохраняется и исход conflict. Если место освободилось позже, повтор прежнего запроса всё равно вернёт прежний конфликт. Новая попытка бронирования после изменения условий должна быть новым запросом.

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

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

В нашем примере создание заявки стоит третьим. В реакции committed её ID доступен как {{ctx.booking_result.operations.2.record_id}}: нумерация операций начинается с нуля. Общий исход находится в {{ctx.booking_result.status}}. При конфликте ID откатившихся операций не возвращаются.

Если переставили операции, проверьте номер в путях к их результатам: operations.2 всегда означает текущую третью операцию.

Подтверждение после сохранения

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

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

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

Для групп и каналов такое подтверждение сейчас поддерживается в Telegram при подтверждённых актуальных правах бота на публикацию. В группах других каналов отключите эту настройку и добавьте обычную отправку по исходу committed: это отдельное действие, которое не входит в атомарное сохранение. Личные диалоги поддерживаются и с нечисловыми ID, если подтверждена связь адресата с текущим пользователем.

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

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

Пределы одного запуска

ДействиеПредел
Прочитать страницу коллекцииПо умолчанию 100, максимум 1000 строк
Размер результата страницыДо 16 МиБ
Фильтры страницыДо 64 условий
Сортировка страницыДо 8 полей
Выбранные поля страницыДо 256 полей
Курсор1 час от начала просмотра
Итоги всей коллекцииДо 4 полей группировки и 64 метрик
Результат группировкиДо 10 000 групп и 16 МиБ
ТранзакцияДо 32 операций и 16 коллекций одного проекта
Подтверждение транзакцииДо 4 текстовых сообщений, каждое до 4096 байт UTF-8 и в пределах лимита канала
Данные транзакцииДо 1 МиБ

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

Связанные материалы: коллекции, обработка списков, блоки с входами и выходами.