При разработке отчетов, сервисных обработок или механизмов анализа движений документа часто возникает задача обойти наборы записей не в произвольном порядке, а строго упорядоченно — например, по алфавиту имен метаданных регистров или по их синонимам для корректного отображения пользователю.
Разберем природу этого ограничения платформы и выясним, какие существуют оптимальные способы решения задачи без лишней потери производительности и с сохранением типов объектов наборов записей.
Проанализируем архитектуру платформы «1С:Предприятие». У документа существуют две сущности, связанные с движениями:
КоллекцияОбъектовМетаданных (доступна через Метаданные.Документы[ИмяДокумента].Движения) — коллекция метаданных, описывающая состав регистров, доступная только для чтения.КоллекцияДвижений (доступна в контексте объекта документа через свойство Движения) — системный объект времени выполнения, содержащий коллекции наборов записей конкретных регистров (например, РегистрНакопленияНаборЗаписей, РегистрБухгалтерииНаборЗаписей).Обе коллекции являются неупорядоченными. Платформа намеренно не предоставляет для них методов Сортировать(), Сдвинуть() или Вставить(), поскольку внутренняя логика системы не требует строгого порядка размещения наборов записей в памяти. Поэтому изменить порядок элементов внутри самого объекта Движения невозможно.
Часто разработчики ошибочно полагают, что для упорядочивания придется создавать громоздкую таблицу значений со всеми полями регистров и сложной типизацией. На самом деле задача решается значительно проще — через сортировку ссылок на наборы или сортировку имен регистров.
Рассмотрим самый компактный подход. Объект СписокЗначений позволяет хранить в поле Значение ссылку на сам набор записей или объект метаданных, а в поле Представление — строковое имя или синоним регистра, по которому требуется выполнить сортировку.
Посмотрим на пример кода:
СписокДвижений = Новый СписокЗначений;
// Заполняем список: значением будет сам набор записей, представлением — имя регистра
Для Каждого НаборЗаписей Из ЭтотОбъект.Движения Цикл
ИмяРегистра = НаборЗаписей.Метаданные().Имя;
СписокДвижений.Добавить(НаборЗаписей, ИмяРегистра);
КонецЦикла;
// Сортируем список по алфавиту имен
СписокДвижений.СортироватьПоПредставлению();
// Обходим элементы в отсортированном порядке с сохранением типа объекта
Для Каждого ЭлементСписка Из СписокДвижений Цикл
ТекущийНабор = ЭлементСписка.Значение;
// Работаем с набором записей (тип полностью сохранен)
Сообщить("Обработка регистра: " + ТекущийНабор.Метаданные().Имя);
КонецЦикла;
Если требуется выводить движения в понятном для пользователя виде, отсортируем элементы по синониму. Для этого достаточно передавать в представление свойство НаборЗаписей.Метаданные().Синоним.
Разберем ситуацию, когда не требуется перекладывать сами наборы записей в стороннюю коллекцию. Мы можем отсортировать только имена регистров, а затем получать наборы записей из исходного объекта Движения.
Важный момент: не следует использовать вложенные циклы для поиска набора по имени, так как это дает квадратичную сложность алгоритма. У объекта КоллекцияДвижений есть штатный метод Найти(), а также обращение по индексу-имени через оператор [], работающие за постоянное время O(1).
Посмотрим на реализацию через массив или список имен:
СписокИмен = Новый СписокЗначений;
Для Каждого НаборЗаписей Из ЭтотОбъект.Движения Цикл
СписокИмен.Добавить(НаборЗаписей.Метаданные().Имя);
КонецЦикла;
СписокИмен.СортироватьПоЗначению();
Для Каждого ЭлементИмени Из СписокИмен Цикл
ИмяРегистра = ЭлементИмени.Значение;
// Получаем набор записей напрямую по имени
ТекущийНабор = ЭтотОбъект.Движения.Найти(ИмяРегистра);
Если ТекущийНабор <> Неопределено Тогда
// Выполняем требуемые действия с движениями
КонецЕсли;
КонецЦикла;
Проанализируем случай, когда сортировку необходимо выполнить по нескольким критериям (например, сначала по типу регистра — регистры накопления, сведений, бухгалтерии, а внутри типа — по синониму).
В платформе 1С колонки таблицы значений по умолчанию имеют произвольный тип, если не задано описание типов. Это позволяет хранить сам объект набора записей в отдельной колонке без ручного описания типов полей.
ТаблицаСортировки = Новый ТаблицаЗначений;
ТаблицаСортировки.Колонки.Добавить("Синоним", Новый ОписаниеТипов("Строка"));
ТаблицаСортировки.Колонки.Добавить("Набор"); // Произвольный тип данных
Для Каждого НаборЗаписей Из ЭтотОбъект.Движения Цикл
НоваяСтрока = ТаблицаСортировки.Добавить();
НоваяСтрока.Синоним = НаборЗаписей.Метаданные().Синоним;
НоваяСтрока.Набор = НаборЗаписей;
КонецЦикла;
ТаблицаСортировки.Сортировать("Синоним Возр");
Для Каждого СтрокаТЗ Из ТаблицаСортировки Цикл
НаборЗаписей = СтрокаТЗ.Набор;
// НаборЗаписей сохраняет все свои свойства и методы
КонецЦикла;
Если упорядочивание наборов записей требуется не для интерфейса, а для фиксации данных в СУБД с целью предотвращения взаимоблокировок (deadlocks), обратим внимание на следующий нюанс:
ЭтотОбъект.Движения.Записать() производит запись в порядке, предопределенном ядром платформы. Изменение порядка обхода в коде никак не повлияет на этот вызов.Записать() у каждого конкретного набора записей индивидуально: ТекущийНабор.Записать().Если задача заключается исключительно в выводе записей в отчет или форму обработки, обращение к коллекции Движения в памяти может быть неоптимальным: наборы записей требуют вызова метода Прочитать(), если они не были прочитаны ранее.
В таких сценариях эффективнее использовать язык запросов или Систему компоновки данных (СКД), объединяя запросы к реальным таблицам движений с отбором ГДЕ Регистратор = &ДокументСсылка и задавая порядок вывода через секцию УПОРЯДОЧИТЬ ПО.