В процессе разработки прикладных решений на платформе 1С:Предприятие 8 программисты часто сталкиваются с необходимостью манипулирования наборами записей регистров сведений. Одной из неочевидных задач является сортировка элементов внутри объекта типа РегистрСведенийНаборЗаписей. Напрямую у этого объекта нет стандартного метода Сортировать(), как у таблицы значений. Однако существует несколько эффективных обходных путей. В этой статье мы подробно разберем, зачем нужна такая сортировка, и детально проанализируем четыре практических метода реализации этой задачи, включая современный подход «на месте» без перезаписи объектов в памяти.
Прежде чем перейти к программному коду, давайте выясним причину возникновения такой потребности. Сортировка набора записей имеет разное значение в зависимости от типа регистра сведений:
НомерСтроки (начиная с 1) в соответствии с тем, как строки расположены в коллекции в памяти. Сортировка подчиненного набора записей перед вызовом метода Записать() — это единственный способ физически изменить нумерацию строк в базе данных, что напрямую влияет на порядок вывода движений в печатных формах документов.Рассмотрим подробнее каждый из способов сортировки коллекции записей регистра.
---
Это самый популярный, понятный и часто используемый способ среди разработчиков. Схема проста: мы выгружаем записи во временную таблицу значений, сортируем ее стандартным методом платформы и загружаем обратно в исходный объект.
Разберем по шагам пример кода:
// 1. Инициализируем набор записей и читаем его из базы данных
НаборЗаписей = РегистрыСведений.ЦеныНоменклатуры.СоздатьНаборЗаписей();
НаборЗаписей.Отбор.Регистратор.Установить(СсылкаНаДокумент);
НаборЗаписей.Прочитать();
// 2. Выгружаем данные набора в Таблицу Значений
ТаблицаДанных = НаборЗаписей.Выгрузить();
// 3. Выполняем сортировку таблицы по нужным колонкам
// В данном примере сортируем по Номенклатуре (возрастание) и Цене (убывание)
ТаблицаДанных.Сортировать("Номенклатура Возр, Цена Уб");
// 4. Загружаем отсортированные данные обратно в набор записей
НаборЗаписей.Загрузить(ТаблицаДанных);
// 5. Записываем измененный набор в базу данных (при необходимости)
НаборЗаписей.Записать();
Преимущества метода:
Недостатки метода:
Загрузить() платформа полностью уничтожает старые строки набора в памяти и создает новые. Если этот набор записей был визуально связан с элементами интерфейса (например, с динамической таблицей на форме), это приведет к потере фокуса текущей строки, сбросу выделения и неприятному визуальному «морганию» формы для пользователя.---
Интересной альтернативой является использование объекта ПостроительЗапроса. Этот объект умеет принимать в качестве источника данных коллекции в памяти, описывать их структуру и выполнять сортировку без написания сложных текстов запросов.
Посмотрим на пример практической реализации данного подхода:
НаборЗаписей = РегистрыСведений.ЦеныНоменклатуры.СоздатьНаборЗаписей();
НаборЗаписей.Отбор.Регистратор.Установить(СсылкаНаДокумент);
НаборЗаписей.Прочитать();
// Инициализируем построитель запроса
ПостроительЗапроса = Новый ПостроительЗапроса;
// Передаем набор записей в качестве источника данных
ПостроительЗапроса.ИсточникДанных = Новый ОписаниеИсточникаДанных(НаборЗаписей);
// Программно настраиваем порядок сортировки
ПостроительЗапроса.Порядок.Добавить("Цена", , , НаправлениеСортировки.Возр);
// Выполняем построение и выгружаем результат обратно в набор
ПостроительЗапроса.Выполнить();
НаборЗаписей.Загрузить(ПостроительЗапроса.Result.Выгрузить());
Особенности метода: Этот способ лаконичен и удобен, когда критерии сортировки необходимо задавать динамически. Однако помните, что в современной практике разработки (особенно при прохождении сертификации «1С:Совместимо» или работе в облачном сервисе 1C:Fresh) ПостроительЗапроса считается устаревшим механизмом (legacy). Вместо него рекомендуется использовать Систему компоновки данных (СКД).
---
Для сохранения методологической чистоты в управляемом приложении мы можем использовать СКД. Мы передаем наш набор записей как внешний набор данных, настраиваем структуру порядка и выгружаем результат. Этот метод работает на любых типах клиентов и полностью соответствует стандартам разработки фирмы «1С». Чтобы упростить программную выгрузку результата, можно использовать универсальную функцию для расчета СКД в коллекцию значений.
Рассмотрим структуру реализации этого подхода:
// Создаем схему компоновки данных программно
СхемаСКД = Новый СхемаКомпоновкиДанных;
// Добавляем источник данных и набор данных - объект
ИсточникДанных = СхемаСКД.ИсточникиДанных.Добавить();
ИсточникДанных.Имя = "ИсточникДанных1";
ИсточникДанных.ТипИсточникаДанных = "Local";
НаборДанных = СхемаСКД.НаборыДанных.Добавить(Тип("НаборДанныхОбъектСхемыКомпоновкиДанных"));
НаборДанных.Имя = "ВходнойНабор";
НаборДанных.ИмяОбъекта = "ВходнойНабор";
// Описываем поля, по которым будем осуществлять сортировку
ПолеНоменклатура = НаборДанных.Поля.Добавить(Тип("ПолеНабораДанныхСхемыКомпоновкиДанных"));
ПолеНоменклатура.Поле = "Номенклатура";
ПолеНоменклатура.ПутьКДанным = "Номенклатура";
// Задаем настройки по умолчанию (сортировка)
КомпоновщикНастроек = Новый КомпоновщикНастроекКомпоновкиДанных;
КомпоновщикНастроек.Инициализировать(Новый ИсточникДоступныхНастроекКомпоновкиДанных(СхемаСКД));
ЭлементПорядка = КомпоновщикНастроек.Настройки.Порядок.Элементы.Добавить(Тип("ЭлементПорядкаКомпоновкиДанных"));
ЭлементПорядка.Поле = Новый ПолеКомпоновкиДанных("Номенклатура");
ЭлементПорядка.ТипУпорядочивания = НаправлениеСортировкиКомпоновкиДанных.Возр;
// Компонуем результат во временную таблицу значений
ВнешниеНаборы = Новый Структура("ВходнойНабор", НаборЗаписей.Выгрузить());
КомпоновщикМакета = Новый КомпоновщикМакетаКомпоновкиДанных;
МакетКомпоновки = КомпоновщикМакета.Выполнить(СхемаСКД, КомпоновщикНастроек.Настройки,,, Тип("ГенераторМакетаКомпоновкиДанныхДляКоллекцииЗначений"));
ПроцессорКомпоновки = Новый ПроцессорКомпоновкиДанных;
ПроцессорКомпоновки.Инициализировать(МакетКомпоновки, ВнешниеНаборы);
ПроцессорВывода = Новый ПроцессорВыводаРезультатаКомпоновкиДанныхВКоллекциюЗначений;
ТаблицаРезультат = Новый ТаблицаЗначений;
ПроцессорВывода.УстановитьОбъект(ТаблицаРезультат);
ПроцессорВывода.Вывести(ПроцессорКомпоновки);
// Возвращаем результат в исходный набор записей
НаборЗаписей.Загрузить(ТаблицаРезультат);
---
А теперь рассмотрим наиболее изящный с точки зрения работы интерфейса метод. Объект типа РегистрСведенийНаборЗаписей обладает встроенным методом Сдвинуть(<Запись>, <Смещение>). Этот метод позволяет перемещать строки внутри набора без их пересоздания.
Использование метода Сдвинуть() позволяет реализовать любой классический алгоритм сортировки (например, сортировку вставками или пузырьковую сортировку) непосредственно внутри набора записей. При этом ссылки на объекты строк в памяти сохраняются, интерфейс не «моргает», а выделение строк пользователем на форме не сбрасывается.
Давайте посмотрим на пример реализации классического алгоритма сортировки пузырьком прямо внутри набора:
НаборЗаписей = РегистрыСведений.ЦеныНоменклатуры.СоздатьНаборЗаписей();
НаборЗаписей.Отбор.Регистратор.Установить(СсылкаНаДокумент);
НаборЗаписей.Прочитать();
КоличествоЗаписей = НаборЗаписей.Количество();
Компаратор = Новый СравнениеЗначений;
// Реализуем алгоритм сортировки непосредственно в наборе записей
Для Шаг1 = 0 По КоличествоЗаписей - 1 Цикл
Для Шаг2 = 0 По КоличествоЗаписей - Шаг1 - 2 Цикл
ТекущаяЗапись = НаборЗаписей[Шаг2];
СледующаяЗапись = НаборЗаписей[Шаг2 + 1];
// Сравниваем значения ключевого поля (например, Цена)
Если Компаратор.Сравнить(ТекущаяЗапись.Цена, СледующаяЗапись.Цена) > 0 Тогда
// Сдвигаем текущую запись на одну позицию вперед, фактически меняя ее местами со следующей
НаборЗаписей.Сдвинуть(ТекущаяЗапись, 1);
КонецЕсли;
КонецЦикла;
КонецЦикла;
Преимущества метода:
---
В рамках обсуждения на форуме был поднят интересный вопрос: как с минимальными усилиями проверить, упорядочена ли уже коллекция, чтобы избежать ресурсоемких операций повторной сортировки?
Для решения этой задачи в памяти оптимальным является обход коллекции в один проход со сложностью алгоритма O(N). Мы последовательно сравниваем текущий элемент с предыдущим с помощью универсального объекта СравнениеЗначений. Это позволяет корректно сравнивать не только числа или строки, но и ссылочные типы данных.
Ниже представлен универсальный алгоритм для такой проверки:
Функция КоллекцияОтсортирована(Коллекция, ИмяПоля) Экспорт
КоличествоЭлементов = Коллекция.Количество();
Если КоличествоЭлементов <= 1 Тогда
Возврат Истина; // Коллекция из одного элемента всегда отсортирована
КонецЕсли;
Компаратор = Новый СравнениеЗначений;
Направление = 0; // 0 - направление не определено, 1 - возрастание, -1 - убывание
Для Индекс = 0 По КоличествоЭлементов - 2 Цикл
ТекущееЗначение = Коллекция[Индекс][ИмяПоля];
СледующееЗначение = Коллекция[Индекс + 1][ИмяПоля];
РезультатСравнения = Компаратор.Сравнить(ТекущееЗначение, СледующееЗначение);
Если РезультатСравнения <> 0 Тогда
Если Направление = 0 Тогда
// Определяем направление сортировки по первой несовпадающей паре
Направление = ?(РезультатСравнения < 0, 1, -1);
ИначеЕсли (Направление = 1 И РезультатСравнения > 0) Или (Направление = -1 И РезультатСравнения < 0) Тогда
// Направление сортировки нарушено
Возврат Ложь;
КонецЕсли;
КонецЕсли;
КонецЦикла;
Возврат Истина;
КонецФункции;
Этот алгоритм чрезвычайно эффективен, так как он прекращает выполнение при первом же обнаружении нарушения порядка сортировки и не требует дублирования данных.
Выбор конкретного метода сортировки набора записей в 1С зависит от контекста вашей задачи. Если вам важна скорость разработки и простота — используйте выгрузку в ТаблицуЗначений. Если критически важно поведение интерфейса и удержание фокуса строки пользователем — используйте алгоритм сортировки на месте с помощью метода Сдвинуть().