При разработке отчетов и обработок в среде 1С:Предприятие 8 часто возникает задача упорядочивания строк в таблице значений. Если колонка содержит текстовые данные с номерами (например, 1.Кабинет, 5.Кабинет, 11.Кабинет), стандартный метод Сортировать() выстраивает их в неожиданном для пользователя порядке: 1.Кабинет, затем 11.Кабинет и только потом 5.Кабинет.
Выясним причину такого поведения платформы и подробно разберем практические способы реализации правильной «естественной» сортировки (Natural Sort).
Метод ТаблицаЗначений.Сортировать() выполняет строго лексикографическое (посимвольное по кодам Unicode) сравнение строк. При сравнении строк 11.Кабинет и 5.Кабинет платформа сравнивает первые символы: символ 1 имеет меньший код, чем символ 5, поэтому строка 11.Кабинет оказывается выше в списке.
В платформе 1С:Предприятие встроенного механизма естественного упорядочивания строк без предварительной подготовки данных нет. Объект СравнениеЗначений также не предоставляет пользовательских функций-компараторов для строк. Поэтому упорядочивание выполняется несколькими проверенными способами.
Рассмотрим наиболее производительный и универсальный способ: добавление во временную или рабочую таблицу значений служебной колонки с типом Число, извлечение числовой части номера, сортировка по этой колонке и последующее удаление служебного поля.
Разберем по шагам реализацию данного алгоритма:
ТаблицаЗначений служебную колонку Порядок с типом Число.Сортировать() по новой колонке.Посмотрим на пример программного кода:
// Добавляем служебную числовую колонку
ТЗ.Колонки.Добавить("Порядок", Новый ОписаниеТипов("Число"));
Для Каждого Стр Из ТЗ Цикл
ПозицияТочки = Найти(Стр.Кабинет, ".");
Если ПозицияТочки > 0 Тогда
НомерСтрокой = СокрЛП(Лев(Стр.Кабинет, ПозицияТочки - 1));
Стр.Порядок = Число(НомерСтрокой);
Иначе
Стр.Порядок = 0;
КонецЕсли;
КонецЦикла;
// Выполняем сортировку по числовому полю
ТЗ.Сортировать("Порядок Возр");
// При необходимости удаляем временную колонку
ТЗ.Колонки.Удалить("Порядок");
Если разработка ведется в типовой конфигурации (например, ЗУП 3.1, ERP 2, УТ 11, БП 3.0), проанализируем возможности библиотеки стандартных подсистем (БСП). Общий модуль СтроковыеФункцииКлиентСервер содержит готовые инструменты для парсинга и форматирования строк.
Вариант с разложением строки в массив подстрок:
ТЗ.Колонки.Добавить("Порядок", Новый ОписаниеТипов("Число"));
Для Каждого Стр Из ТЗ Цикл
ЧастиСтроки = СтроковыеФункцииКлиентСервер.РазложитьСтрокуВМассивПодстрок(Стр.Кабинет, ".");
Если ЧастиСтроки.Количество() > 0 Тогда
Стр.Порядок = Число(ЧастиСтроки[0]);
КонецЕсли;
КонецЦикла;
ТЗ.Сортировать("Порядок Возр");
ТЗ.Колонки.Удалить("Порядок");
Другой подход заключается в приведении числовых фрагментов к фиксированной длине. Если дополнить числа слева нулями до одинаковой разрядности (например, 01.Кабинет, 05.Кабинет, 11.Кабинет), стандартная лексикографическая сортировка отработает корректно без явного преобразования в число.
С помощью БСП дополнение строки выполняется вызовом одной функции:
ТЗ.Колонки.Добавить("СтрокаДляСортировки", Новый ОписаниеТипов("Строка"));
Для Каждого Стр Из ТЗ Цикл
ПозицияТочки = Найти(Стр.Кабинет, ".");
Если ПозицияТочки > 0 Тогда
Номер = СокрЛП(Лев(Стр.Кабинет, ПозицияТочки - 1));
Остаток = Сред(Стр.Кабинет, ПозицияТочки);
// Приводим номер к фиксированной длине в 5 знаков
Стр.СтрокаДляСортировки = СтроковыеФункцииКлиентСервер.ДополнитьСтроку(Номер, 5, "0", "Слева") + Остаток;
Иначе
Стр.СтрокаДляСортировки = Стр.Кабинет;
КонецЕсли;
КонецЦикла;
ТЗ.Сортировать("СтрокаДляСортировки Возр");
ТЗ.Колонки.Удалить("СтрокаДляСортировки");
Для сложных номеров с несколькими уровнями иерархии (например, 1.12.3, 10.2.1) можно использовать алгоритмическую нормализацию всех составных частей:
Функция ИсправитьНомерДляСортировки(Знач Текст, МинДлинаРаздела = 5) Экспорт
Результат = "";
Разделители = " ,.;-\/:*";
БылРазделитель = Истина;
ТекущийБлок = "";
Для Сч = 1 По СтрДлина(Текст) Цикл
Символ = Сред(Текст, Сч, 1);
Если Найти(Разделители, Символ) > 0 Тогда
Если Не БылРазделитель Тогда
ДлинаБлока = СтрДлина(ТекущийБлок);
Если ДлинаБлока < МинДлинаРаздела Тогда
ТекущийБлок = Лев(" ", МинДлинаРаздела - ДлинаБлока) + ТекущийБлок;
КонецЕсли;
Результат = Результат + ТекущийБлок;
ТекущийБлок = "";
КонецЕсли;
Результат = Результат + Символ;
БылРазделитель = Истина;
Иначе
БылРазделитель = Ложь;
ТекущийБлок = ТекущийБлок + Символ;
КонецЕсли;
КонецЦикла;
Если Не БылРазделитель Тогда
ДлинаБлока = СтрДлина(ТекущийБлок);
Если ДлинаБлока < МинДлинаРаздела Тогда
ТекущийБлок = Лев(" ", МинДлинаРаздела - ДлинаБлока) + ТекущийБлок;
КонецЕсли;
Результат = Результат + ТекущийБлок;
КонецЕсли;
Возврат Результат;
КонецФункции
Рассмотрим вариант, когда данные в таблице значений имеют одинаковый текстовый суффикс (например, 1.Кабинет, 5.Кабинет, 11.Кабинет). В этом случае упорядочивание можно выполнить с помощью объекта Запрос через вычисление длины строки:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ИсходныеДанные.Кабинет КАК Кабинет
|ПОМЕСТИТЬ ВТ_Данные
|ИЗ
| &ТаблицаДанных КАК ИсходныеДанные
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| ВТ.Кабинет КАК Кабинет
|ИЗ
| ВТ_Данные КАК ВТ
|
|УПОРЯДОЧИТЬ ПО
| ДЛИНА(ВТ.Кабинет),
| ВТ.Кабинет";
Запрос.УстановитьПараметр("ТаблицаДанных", ТЗ);
ТЗ = Запрос.Выполнить().Выгрузить();
При сортировке сначала по ДЛИНА(ВТ.Кабинет), а затем по самому значению, одноразрядные номера (длина меньше) будут расположены раньше двухразрядных, что обеспечит корректный порядок строк.
Для предотвращения подобных сложностей рекомендуется соблюдать следующие правила при проектировании структуры таблиц:
001.Кабинет, 002.Кабинет).