В процессе разработки на платформе 1С:Предприятие 8 часто возникает задача обработки больших массивов данных. Выборка всех записей справочника или таблицы значений одним запросом может привести к избыточному потреблению оперативной памяти, блокировкам таблиц и аварийному завершению работы сеанса. Для решения этой проблемы применяется пагинация — разделение выборки на небольшие порции (пакеты) по N элементов. Рассмотрим подробнее, как реализовать этот механизм различными способами, проанализируем их эффективность и разберем конкретные примеры кода. Для безопасной очистки базы от данных отдельной компании без риска блокировок можно использовать обработку для выборочного удаления организаций.
Начнем с самого актуального способа. Долгое время в языке запросов 1С отсутствовал прямой аналог SQL-конструкции OFFSET. Однако, начиная с версии платформы 8.3.25, разработчики получили штатный инструмент для пагинации. Теперь мы можем использовать комбинацию ключевых слов ВЫБРАТЬ ПЕРВЫЕ и ПРОПУСТИТЬ.
Разберем пример запроса, который выбирает вторую «страницу» данных (записи со 101 по 200):
ВЫБРАТЬ ПЕРВЫЕ 100
Номенклатура.Ссылка КАК Ссылка,
Номенклатура.Наименование КАК Наименование
ИЗ
Справочник.Номенклатура КАК Номенклатура
УПОРЯДОЧИТЬ ПО
Наименование,
Ссылка
ПРОПУСТИТЬ 100
Важное замечание: При использовании ПРОПУСТИТЬ обязательным условием является наличие конструкции УПОРЯДОЧИТЬ ПО. Без явной сортировки порядок записей в базе данных не гарантирован, и при получении разных пакетов данные могут дублироваться или пропускаться. Проанализируем ситуацию: платформа транслирует этот запрос в эффективные инструкции СУБД (например, OFFSET...FETCH в MS SQL Server), что обеспечивает высокую скорость работы даже на миллионах записей. Для мониторинга трансляции запросов и анализа нагрузки на стороне СУБД можно использовать обработку для анализа работы SQL-сервера и текущих запросов 1С.
Если ваша информационная база работает на версии платформы ниже 8.3.25, наиболее надежным и производительным методом будет итерация по индексу, в роли которого выступает Ссылка. Этот метод основан на том, что ссылки в 1С можно сравнивать на "больше" или "меньше".
Алгоритм действий выглядит следующим образом: в первом запросе мы выбираем первые 100 записей. Для получения следующего пакета мы передаем в параметры запроса последнюю ссылку из предыдущего пакета. Посмотрим на пример кода:
// Первый запрос или запрос для следующего пакета
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ ПЕРВЫЕ 100
| Сотрудники.Ссылка КАК Ссылка
|ИЗ
| Справочник.Сотрудники КАК Сотрудники
|ГДЕ
| Сотрудники.Ссылка > &ПоследняяСсылка
|УПОРЯДОЧИТЬ ПО
| Ссылка";
Запрос.УстановитьПараметр("ПоследняяСсылка", ПоследняяСсылкаИзПредыдущегоПакета);
Результат = Запрос.Выполнить();
Выясним причину, почему этот метод считается оптимальным. Поле Ссылка всегда проиндексировано. СУБД выполняет поиск по индексу (Index Seek), что происходит практически мгновенно. Это гораздо быстрее, чем использование временных таблиц или сложных соединений.
Рассмотрим ситуацию, когда нам необходимо пронумеровать записи внутри запроса для сложной логики распределения. Для этого можно использовать функцию АВТОНОМЕРЗАПИСИ(), которая доступна при помещении данных во временную таблицу (ВТ).
Разберем по шагам создание такой выборки:
ВЫБРАТЬ
Номенклатура.Ссылка КАК Ссылка,
АВТОНОМЕРЗАПИСИ() КАК НомерЗаписи
ПОМЕСТИТЬ ВТ_Данные
ИЗ
Справочник.Номенклатура КАК Номенклатура
УПОРЯДОЧИТЬ ПО
Ссылка;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
ВТ.Ссылка КАК Ссылка,
ВТ.НомерЗаписи КАК НомерЗаписи,
ВЫРАЗИТЬ((ВТ.НомерЗаписи - 1) / 100 КАК ЧИСЛО(10, 0)) КАК НомерСтраницы
ИЗ
ВТ_Данные КАК ВТ
ГДЕ
ВТ.НомерЗаписи МЕЖДУ &Начало И &Конец
Внимание! У этого метода есть существенный недостаток, упомянутый в обсуждении: при огромных объемах справочника (миллионы записей) создание временной таблицы сопоставимого размера может привести к деградации производительности дисковой подсистемы сервера 1С и СУБД. Проанализируем: этот метод хорош для выборок среднего размера (до 100-200 тысяч записей), где требуется сложная аналитическая обработка. Для сокращения объёма данных в крупных базах без написания собственных запросов очистки можно использовать решение для многопоточной свёртки данных.
Малоизвестный, но эффективный способ — использование встроенных механизмов СКД для постраничного вывода. СКД умеет управлять выборкой на уровне процессора вывода. При программном выполнении отчета мы можем использовать объект ПроцессорВыводаРезультатаКомпоновкиДанныхВКоллекциюЗначений.
Рассмотрим возможности настройки:
В ходе дискуссии предлагался вариант использования ЛЕВОЕ СОЕДИНЕНИЕ таблицы самой с собой для подсчета количества записей, которые «меньше» текущей ссылки (для имитации номера строки). Проанализируем, почему это плохая идея.
Сложность такого запроса составляет O(n²). Если в таблице 1000 записей, СУБД выполнит около 1 000 000 сравнений. Если же записей 100 000, количество операций достигнет 10 миллиардов. Это гарантированно приведет к долгому ожиданию ответа или «падению» службы rphost. Никогда не используйте этот метод для реализации пагинации на реальных данных.
Подведем итог и определимся, какой инструмент выбрать в зависимости от условий задачи:
ПРОПУСТИТЬ. Это самый читаемый и эффективный способ, поддерживаемый на уровне ядра.Ссылка > &ПоследняяСсылка. Это классика, которая работает максимально быстро за счет индексов.АВТОНОМЕРЗАПИСИ(), но следите за объемом данных и не забывайте индексировать временные таблицы по полю номера, если планируете дальнейшие соединения.Помните, что любая пагинация требует стабильной сортировки. Всегда добавляйте уникальное поле (например, Ссылка) в конец списка УПОРЯДОЧИТЬ ПО, чтобы избежать «прыгающих» данных при переходе между страницами.