Пагинация в 1С
Те, кому приходилось делать REST API на базе HTTP-сервисов 1С, могли в какой-то момент столкнуться с необходимостью разработки методов, которые позволяли бы возвращать данные с пагинацией, т.е. последовательными порциями.
В языках общего назначения пагинация реализуется простым использованием операторов OFFSET и LIMIT в SQL-запросе к базе данных. В языке запросов 1С оператора OFFSET нет, поэтому всем приходится решать эту задачу обходными способами.
Один из таких обходных способов представлен в этой статье.
- Описание
- Подробнее
Описание
Предпосылки разработки методов API с пагинацией
В нашей компании разрабатывался сайт на собственном движке, и поскольку источником всех данных для него была учётная система на платформе 1С, а в штате было два программиста 1С, в качестве бэкенда было принято решение разработать отдельную систему на той же 1С.
Весь необходимый API был описан в формате Swagger (OpenAPI) руководителем проекта разработки сайта, его методы мы реализовали с помощью HTTP-сервисов 1С.
Метод API для получения номенклатуры для сайта изначально не предполагал никаких отборов, т.к. до какого-то момента выполнялся за приемлемое время, но спустя несколько месяцев из-за увеличившегося количества номенклатуры и связанных с ней данных метод стал выполняться очень долго, клиент не дожидался ответа.
С таймаутами заморачиваться не стали, т.к. они и так были установлены достаточно большими, по 300 секунд.
Решили сделать версию 2 того же метода, но уже с возможностью пагинации.
Особенности реализации пагинации средствами 1С
При разработке методов с пагинацией нужно учитывать следующие особенности:
- В языке запросов 1С нет оператора OFFSET, соответственно, смещение записей для получения определённой порции данных нужно реализовать как-то иначе.
Ограничить количество записей можно с помощью оператора ВЫБРАТЬ ПЕРВЫЕ {количество объектов в порции}, тут всё очевидно.
Разве что количество объектов в порции придётся подставлять через СтрШаблон(), СтрЗаменить() или конкатенацию.
Но как выбрать только записи, начиная с определённой?
Логичным решением выглядит нумерация записей и последующий отбор по номеру.
В запросах 1С доступна функция АВТОНОМЕРЗАПИСИ(), которая последовательно присваивает номер записи, начиная с 1, полю с вызовом этой функции.
А дальше по этому номеру можно отобрать нужную порцию данных.
Это позволит не выбирать все данные с последующим получением нужных данных в коде, например, по индексам, а получать сразу готовую коллекцию записей с помощью запроса.
- Запросы к методу с одними и теми же полями "limit" (количество объектов в порции данных) и "next"* (номер первого объекта следующей порции) в теле запроса всегда** должны возвращать один и тот же набор объектов.
* Названия полей приведены так, как они указаны у нас, вы можете их называть, разумеется, на ваше усмотрение.
** Передача тела запроса предполагает запрос POST, т.е. об идемпотентности тут речи нет, но метод, возвращающий разные результаты при одних и тех же параметрах запроса, должен вызывать вопросы у клиента. Для большей очевидности (в плане ожидания идемпотентности) можно было бы реализовать метод GET с параметрами в строке запроса, но мы пошли по пути POST с телом JSON.
Тут тоже всё вроде бы очевидно: данные должны быть отсортированы, чтобы одни и те же номера всегда присваивались одним и тем же записям.
Для этого лучше всего подходит код элемента в справочнике, т.к. коды в справочниках никто при нормальном учёте не меняет, а уникальный идентификатор нового объекта теоретически может быть сгенерирован таким, что при сортировке по ссылке новый элемент справочника попадёт куда-то в середину списка. Если ошибаюсь, поправьте, пожалуйста.
- Запросы к методу должны возвращать именно запрошенное (или меньшее) количество записей.
Хотя левое соединение с другими таблицами может приводить к увеличению количества записей, изначально было не вполне очевидно, что возвращаемое методом количество записей может быть больше запрашиваемого.
Чтобы этого избежать, основные данные предварительно нужно получать отдельно в запрашиваемом количестве и только потом связывать с дополнительными данными. В последней порции данных может быть меньше.
Из-за того, что данные временных таблиц в общем случае*** упорядочивать нельзя, связывание основных данных с дополнительными нужно выполнять в дополнительном запросе.
*** Можно в случае использования оператора ВЫБРАТЬ ПЕРВЫЕ.
Пример реализации
Часть комментариев добавил для пояснения тех или иных решений, остальные комментарии и описание взяты из фактической реализации.
Многоточием в тексте запросов обозначены прочие поля.
В первом запросе получаем, фактически, все записи таблицы. Дополнительно не оптимизировали, т.к. отрабатывает быстро, да и клиент у этого метода всего один.