Сортировка ДереваЗначений: все уровни, итоги, ссылки и строки
Содержание
Дерево значений сортируют методом коллекции строк: Дерево.Строки.Сортировать("Наименование", Истина). Первый параметр — список колонок с направлением, второй — сортировать ли вложенные строки. С Истина платформа упорядочит все уровни сразу, и подчинённые строки останутся у своих родителей.
Сам объект ДеревоЗначений метода сортировки не имеет. Метод есть у коллекции Строки — и у корня дерева, и у любой его строки. Поэтому можно отсортировать всё дерево, одну ветку или только верхний уровень. Ниже — синтаксис, сортировка по нескольким колонкам, разные правила для разных уровней, итоги, строки и ссылки, работа на форме и скорость.
Метод Сортировать: синтаксис и как он работает
Полный вызов выглядит так:
КоллекцияСтрок.Сортировать(<Колонки>, <СортироватьВложенные>, <ОбъектСравнения>);
| Параметр | Тип | Что задаёт |
|---|---|---|
| Колонки | Строка | Имена колонок через запятую, после имени — Возр или Убыв |
| СортироватьВложенные | Булево | Истина — все уровни ниже, Ложь (по умолчанию) — только эта коллекция |
| ОбъектСравнения | СравнениеЗначений | Как сравнивать ссылки: по представлению, а не по внутреннему идентификатору |
Что происходит при вызове:
- 🔹 строки коллекции переставляются на месте, новое дерево не создаётся;
- 🔹 вместе со строкой переезжают все её потомки — иерархия не ломается;
- 🔹 без второго параметра порядок меняется только на том уровне, у которого вызвали метод;
- 🔹 направление по умолчанию — по возрастанию,
Возрможно не писать.
Простейший пример — дерево подразделений, где у каждой строки есть колонка Наименование:
// Только верхний уровень
Дерево.Строки.Сортировать("Наименование");
// Все уровни дерева
Дерево.Строки.Сортировать("Наименование", Истина);
// Одна ветка: дети и внуки третьей строки верхнего уровня
Ветка = Дерево.Строки[2];
Ветка.Строки.Сортировать("Наименование", Истина);
Если дерево собирали вручную через Строки.Добавить(), не сортируйте после каждого добавления. Наполните дерево целиком и вызовите Сортировать один раз в конце.
Сортировка дерева значений по нескольким колонкам
Отдельного метода вроде «установить сортировку» у дерева нет. Сложный порядок задают одной строкой в первом параметре. Колонки перечисляют через запятую, направление указывают для каждой.
Дерево.Строки.Сортировать("ЭтоГруппа Убыв, Наименование", Истина);
Здесь на каждом уровне сначала идут группы (Истина больше Ложь, поэтому Убыв ставит их наверх), а внутри групп и внутри элементов — алфавит. Так выглядит привычный список номенклатуры: папки сверху, товары под ними.
Ещё несколько рабочих комбинаций:
| Задача | Строка колонок |
|---|---|
| Самые крупные суммы сверху, при равенстве — по дате | "Сумма Убыв, Дата" |
| Новые документы первыми | "Дата Убыв, Номер Убыв" |
| Сотрудники по подразделению, затем по ФИО | "Подразделение, ФИО" |
| Код как число, а не как строка | "КодЧисло" (служебная колонка, см. ниже) |
Ключевые слова можно писать и по-английски: Asc и Desc. Регистр букв не важен.
Своё правило сравнения через служебную колонку
В языке 1С нельзя передать в Сортировать свою функцию сравнения. Если нужен порядок, которого нет в данных, — заведите колонку-ключ, заполните её и сортируйте по ней.
Пример: статусы заказов должны идти не по алфавиту, а по жизненному циклу.
Дерево.Колонки.Добавить("ПорядокСтатуса", Новый ОписаниеТипов("Число"));
Порядок = Новый Соответствие;
Порядок.Вставить("Новый", 1);
Порядок.Вставить("В работе", 2);
Порядок.Вставить("Отгружен", 3);
Порядок.Вставить("Закрыт", 4);
ЗаполнитьКлючСортировки(Дерево.Строки, Порядок);
Дерево.Строки.Сортировать("ПорядокСтатуса, Дата", Истина);
Процедура ЗаполнитьКлючСортировки(СтрокиДерева, Порядок)
Для Каждого Строка Из СтрокиДерева Цикл
Ключ = Порядок.Получить(Строка.Статус);
Строка.ПорядокСтатуса = ?(Ключ = Неопределено, 99, Ключ);
ЗаполнитьКлючСортировки(Строка.Строки, Порядок);
КонецЦикла;
КонецПроцедуры
Неизвестный статус получает 99 и уходит в конец, а не в начало. После сортировки колонку можно удалить: Дерево.Колонки.Удалить("ПорядокСтатуса").
Рекурсивная сортировка: разные правила для разных уровней
Флаг СортироватьВложенные = Истина применяет одно правило ко всем уровням. Часто этого мало. Типичный отчёт: на первом уровне склады по алфавиту, на втором — товары по сумме остатка от большей к меньшей.
Решение — своя рекурсивная процедура. На каждом уровне она вызывает Сортировать без флага вложенных, а потом спускается к детям:
Процедура СортироватьПоУровням(СтрокиДерева, Уровень = 0)
Если СтрокиДерева.Количество() < 2 Тогда
// Сортировать нечего, но у единственной строки могут быть дети
ИначеЕсли Уровень = 0 Тогда
СтрокиДерева.Сортировать("Склад");
Иначе
СтрокиДерева.Сортировать("Сумма Убыв, Номенклатура");
КонецЕсли;
Для Каждого Строка Из СтрокиДерева Цикл
Если Строка.Строки.Количество() > 0 Тогда
СортироватьПоУровням(Строка.Строки, Уровень + 1);
КонецЕсли;
КонецЦикла;
КонецПроцедуры
// Вызов
СортироватьПоУровням(Дерево.Строки);
Уровень можно не передавать параметром, а брать у строки: Строка.Уровень() возвращает номер уровня, верхний — 0. Параметр удобнее, когда вы сортируете ветку и считаете её уровень «первым».
Где рекурсия нужнее встроенного флага:
- 📂 разные колонки сортировки на разных уровнях;
- 📂 часть веток надо оставить в исходном порядке (например, раздел «Прочее» — как пришёл из запроса);
- 📂 сортировка только до определённой глубины — добавьте условие
Если Уровень < 2.
Не удаляйте и не добавляйте строки в коллекцию внутри того же цикла Для Каждого, которым её обходите. Перестановка методом Сортировать для текущего уровня делается до цикла — так, как в примере выше.
Как отсортировать дерево с итогами и группировками
Дерево с итогами обычно получают из запроса:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Остатки.Склад КАК Склад,
| Остатки.Номенклатура КАК Номенклатура,
| Остатки.СуммаОстаток КАК Сумма
|ИЗ
| РегистрНакопления.ТоварыНаСкладах.Остатки КАК Остатки
|ИТОГИ
| СУММ(Сумма)
|ПО
| Склад";
Дерево = Запрос.Выполнить().Выгрузить(ОбходРезультатаЗапроса.ПоГруппировкам);
Имя регистра здесь условное — подставьте свой. Важно другое: в строке группировки колонка Сумма уже содержит итог по складу. Поэтому одна команда сортирует и группы, и детали:
Дерево.Строки.Сортировать("Сумма Убыв", Истина);
Склады встанут по убыванию общего остатка, а товары внутри каждого склада — по убыванию своей суммы.
Сортировать в запросе или в дереве
| Способ | Когда подходит | Ограничения |
|---|---|---|
УПОРЯДОЧИТЬ ПО в запросе | Порядок деталей внутри групп, большие выборки | Порядок самих групп по итогу задать сложнее |
Сортировать после выгрузки | Группы по итогам, смена порядка без повторного запроса | Работает в памяти, на очень больших деревьях медленнее |
| Своя рекурсия | Разные правила по уровням | Больше кода, его надо проверять |
Строка «Итого» всегда внизу
Если вы добавили в дерево итоговую строку вручную, сортировка утащит её в середину. Лечится служебной колонкой: обычным строкам — 0, итоговой — 1.
Дерево.Строки.Сортировать("ЭтоИтог, Сумма Убыв");
Сортировка по возрастанию ЭтоИтог всегда оставит итоговую строку последней.
Если итоги после правок дерева надо пересчитать, у коллекции строк есть метод Итог: Строка.Строки.Итог("Сумма") вернёт сумму колонки по подчинённым строкам.
Строки, буквы и ссылки: почему порядок «не тот»
Самые частые жалобы на сортировку дерева касаются не метода, а данных в колонках.
Сортировка по представлению ссылок
Колонка Номенклатура содержит ссылки. Чтобы получить алфавит, а не случайный на вид порядок:
Сравнение = Новый СравнениеЗначений;
Дерево.Строки.Сортировать("Номенклатура", Истина, Сравнение);
Объект СравнениеЗначений сравнивает ссылки по представлению. Цена — платформе приходится получать представление каждой ссылки. На тысячах строк это заметно, об этом в разделе про скорость.
Ключ с ведущими нулями
Артикулы вида «А-2», «А-10», «А-101» в строковой колонке встают как «А-10», «А-101», «А-2». Выделите числовую часть и сортируйте по ней:
Строка.КлючАртикула = СтрШаблон("%1%2",
Лев(Строка.Артикул, 2),
Формат(Число(Сред(Строка.Артикул, 3)), "ЧЦ=10; ЧВН=; ЧГ=0"));
Пример рассчитан на артикулы строго формата «буква-дефис-число». Для других шаблонов разбор строки придётся написать под свои данные, а Число() обернуть в Попытка, чтобы мусорный артикул не остановил обработку.
Локаль информационной базы задают в Конфигураторе: Администрирование → Региональные установки информационной базы. Меняйте её только осознанно: от неё зависит не только сортировка, но и формат чисел и дат у всех пользователей.
Сортировка дерева на управляемой форме
На форме дерево хранится как ДанныеФормыДерево. У его коллекций метода Сортировать нет. Стандартный приём — перенести данные на сервер, отсортировать как обычное дерево и вернуть:
&НаСервере
Процедура СортироватьДеревоНаСервере(Колонки)
Дерево = РеквизитФормыВЗначение("ДеревоТоваров");
Дерево.Строки.Сортировать(Колонки, Истина);
ЗначениеВРеквизитФормы(Дерево, "ДеревоТоваров");
КонецПроцедуры
&НаКлиенте
Процедура КомандаСортироватьПоСумме(Команда)
СортироватьДеревоНаСервере("Сумма Убыв");
КонецПроцедуры
Что учесть:
- 🖥 после
ЗначениеВРеквизитФормытекущая строка и раскрытые узлы сбрасываются — если нужно, запомните идентификатор текущей строки и раскройте узлы заново черезЭлементы.ДеревоТоваров.Развернуть(); - 🖥 каждый вызов — это серверный вызов и передача всего дерева, поэтому не сортируйте при каждом изменении ячейки;
- 🖥 если дерево — реквизит формы обработки или отчёта, удобно хранить текущий порядок в отдельном реквизите и показывать его в заголовке колонки.
Скорость: сортировка больших деревьев
Встроенный Сортировать работает быстро — это код платформы, а не встроенного языка. Тормозит обычно то, что вокруг него.
Что замедляет:
- 🐢 самописная «пузырьковая» сортировка через
Сдвинуть— на тысячах строк она в разы медленнее встроенного метода; - 🐢 сортировка внутри цикла наполнения — каждый вызов переставляет всю коллекцию заново;
- 🐢
СравнениеЗначенийпо ссылкам на большом объёме — платформа запрашивает представления; - 🐢 перенос большого дерева между клиентом и сервером ради одной сортировки.
Что ускоряет:
- Задайте порядок деталей в запросе через
УПОРЯДОЧИТЬ ПО, а в памяти доупорядочьте только группы. - Вместо
СравнениеЗначенийвыберите в запросе представление:Остатки.Номенклатура.Представление КАК НоменклатураПредставление— и сортируйте по этой строковой колонке. - Сортируйте один раз, после полного наполнения дерева.
- Сортируйте ветку, а не всё дерево, если поменялась одна ветка.
- Ключи сортировки (числа, нормализованные строки) считайте один раз при наполнении, а не в отдельном проходе.
У дерева значений нет индексов, в отличие от таблицы значений. Если по нему много ищут через НайтиСтроки(..., Истина), иногда выгоднее держать рядом плоскую таблицу с индексом для поиска, а дерево — только для вывода.
Замерить, что именно тормозит, проще всего замером производительности в режиме отладки: он покажет строки кода с наибольшим временем.