Сортировка СписокЗначений, массивов и таблиц во встроенном языке 1С
Содержание
Список значений в 1С сортируют двумя встроенными методами: СортироватьПоЗначению() и СортироватьПоПредставлению(). Оба принимают направление — НаправлениеСортировки.Возр или НаправлениеСортировки.Убыв — и меняют порядок элементов прямо в том же списке, без создания копии.
Типичная ситуация: в обработке собрали список складов или договоров для выбора пользователем, а он вывелся в порядке добавления. Или нужно отсортировать массив, у которого своего метода сортировки нет. Ниже — все рабочие способы обработки данных: от одной строки кода до сортировки в запросе, с разбором подводных камней по типам и объёмам.
Что умеет сам объект СписокЗначений
У списка значений нет свойства «порядок сортировки», которое достаточно один раз задать. Сортировка — это действие: вызвали метод, элементы переставились, дальше список живёт в новом порядке. Новые элементы, добавленные через Добавить(), встанут в конец, а не на «своё» место.
Каждый элемент списка хранит четыре вещи:
- 🔹
Значение— сами данные: число, строка, дата, ссылка, что угодно; - 🔹
Представление— строка, которую видит пользователь; - 🔹
Пометка— флажок (Булево); - 🔹
Картинка— значок элемента.
Сортировать встроенными средствами можно по первым двум. По пометке и картинке готового метода нет — это решается через таблицу значений (пример ниже).
Для ручной перестановки есть метод Сдвинуть(Элемент, Смещение): он перемещает элемент на указанное число позиций вверх (отрицательное смещение) или вниз. Для сортировки он не годится, а для кнопок «вверх/вниз» на форме — в самый раз.
Список значений доступен и на сервере, и на клиенте. Сортировать его можно прямо в клиентском обработчике формы, без серверного вызова.
Методы СортироватьПоЗначению и СортироватьПоПредставлению
Базовый пример, который стоит запомнить целиком:
Список = Новый СписокЗначений;
Список.Добавить(30, "Тридцать");
Список.Добавить(5, "Пять");
Список.Добавить(12, "Двенадцать");
Список.СортироватьПоЗначению();
// 5, 12, 30
Список.СортироватьПоЗначению(НаправлениеСортировки.Убыв);
// 30, 12, 5
Список.СортироватьПоПредставлению();
// Двенадцать, Пять, Тридцать
Параметр направления необязательный. Если его не указать, сортировка идёт по возрастанию.
Как выбрать метод:
| Задача | Метод | Что получим |
|---|---|---|
| Числа, даты по порядку | СортироватьПоЗначению() | 5, 12, 30 или 01.01, 15.03, 30.06 |
| Строки по алфавиту, значения — строки | СортироватьПоЗначению() | Алфавитный порядок значений |
| Ссылки по наименованию | СортироватьПоПредставлению() | Алфавит по тому, что видит пользователь |
| Значение — код, а показывать надо по названию | СортироватьПоПредставлению() | Порядок по подписи, а не по коду |
| Сначала отмеченные флажком | Через таблицу значений | См. раздел о сложных критериях |
Названия, которых нет у списка значений
В примерах из интернета встречаются Список.Сортировать() и Список.Упорядочить(). У объекта СписокЗначений таких методов нет — код упадёт с ошибкой «Метод объекта не обнаружен
».
Метод Сортировать() есть у других коллекций:
- 📋
ТаблицаЗначений.Сортировать(Колонки, ОбъектСравнения); - 🌳 у коллекции строк дерева значений —
ДеревоЗначений.Строки.Сортировать(...), с возможностью сортировать и подчинённые строки.
А «упорядочить» — это ключевое слово языка запросов УПОРЯДОЧИТЬ ПО, к коллекциям в памяти оно отношения не имеет.
Как отсортировать массив
У массива метода сортировки нет вообще. Стандартный приём — прогнать его через список значений:
Функция ОтсортированныйМассив(Массив, Направление = Неопределено) Экспорт
Если Направление = Неопределено Тогда
Направление = НаправлениеСортировки.Возр;
КонецЕсли;
Список = Новый СписокЗначений;
Список.ЗагрузитьЗначения(Массив);
Список.СортироватьПоЗначению(Направление);
Возврат Список.ВыгрузитьЗначения();
КонецФункции
ЗагрузитьЗначения() копирует элементы в список, исходный массив не меняется. ВыгрузитьЗначения() возвращает новый массив. Значение по умолчанию у параметра — Неопределено, потому что в объявлении параметра нельзя написать НаправлениеСортировки.Возр: там допустимы только литералы.
Сортировка через язык запросов 1С
Когда данные лежат в базе, сортировать их в памяти — лишняя работа. Запрос отдаёт уже упорядоченный результат, и сортировку выполняет СУБД.
Список ссылок, упорядоченный по реквизиту
Частый случай: есть список значений со ссылками на номенклатуру, и его надо выстроить по наименованию или артикулу. Передаём список параметром в условие В (...):
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Номенклатура.Ссылка КАК Ссылка
|ИЗ
| Справочник.Номенклатура КАК Номенклатура
|ГДЕ
| Номенклатура.Ссылка В (&Список)
|
|УПОРЯДОЧИТЬ ПО
| Номенклатура.Наименование";
Запрос.УстановитьПараметр("Список", Список);
Ссылки = Запрос.Выполнить().Выгрузить().ВыгрузитьКолонку("Ссылка");
Список = Новый СписокЗначений;
Список.ЗагрузитьЗначения(Ссылки);
Так можно сортировать по любому реквизиту: коду, артикулу, дате создания, даже по реквизиту связанного объекта через точку. Если в упорядочивании указать саму ссылку (УПОРЯДОЧИТЬ ПО Номенклатура.Ссылка), порядок определит платформа по настройкам объекта. Явное поле надёжнее.
Таблица значений из памяти как источник запроса
Таблицу значений можно передать в запрос и отсортировать там, если нужна сортировка по реквизиту ссылки или сразу по нескольким полям вместе с отбором. Условие одно: сначала поместить её во временную таблицу.
ТЗ = Новый ТаблицаЗначений;
ТЗ.Колонки.Добавить("Номенклатура", Новый ОписаниеТипов("СправочникСсылка.Номенклатура"));
ТЗ.Колонки.Добавить("Количество", Новый ОписаниеТипов("Число"));
// ... заполнение ТЗ
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Т.Номенклатура КАК Номенклатура,
| Т.Количество КАК Количество
|ПОМЕСТИТЬ ВТ
|ИЗ
| &Таблица КАК Т
|;
|
|////////////////////////////////////////
|ВЫБРАТЬ
| ВТ.Номенклатура КАК Номенклатура,
| ВТ.Количество КАК Количество
|ИЗ
| ВТ КАК ВТ
|
|УПОРЯДОЧИТЬ ПО
| ВТ.Номенклатура.Наименование,
| Количество УБЫВ";
Запрос.УстановитьПараметр("Таблица", ТЗ);
Результат = Запрос.Выполнить().Выгрузить();
Колонкам таблицы значений обязательно задайте типы через ОписаниеТипов. Без типов запрос выдаст ошибку при помещении во временную таблицу: платформа не знает, какое поле создавать.
Сам список значений источником запроса быть не может — только параметром в условии В (&Список). Если нужна именно выборка из него, сначала переложите данные в таблицу значений.
Сравнение значений и сортировка по своему правилу
Встроенный язык 1С не умеет передавать в метод сортировки собственную функцию сравнения, как это делают в других языках. Но есть два инструмента, которые закрывают почти все задачи.
Объект СравнениеЗначений
СравнениеЗначений сравнивает два значения методом Сравнить() и возвращает -1, 0 или 1:
Сравнение = Новый СравнениеЗначений;
Результат = Сравнение.Сравнить(5, 12); // -1: первое меньше
Главное его применение — второй параметр метода Сортировать() у таблицы значений. Без него ссылочные значения сортируются по внутреннему идентификатору, то есть в порядке, который человеку ничего не говорит. С ним — по представлению:
ТЗ.Сортировать("Номенклатура", Новый СравнениеЗначений);
Приём простой, но про него часто забывают. В итоге отчёт в обработке выводит товары «вразнобой», хотя сортировка в коде вызвана.
Сортировка по нескольким полям и по пометке
Для сложных критериев список значений перекладывают в таблицу значений, где можно указать несколько колонок через запятую. Пример: сначала отмеченные элементы, внутри — по алфавиту.
ТЗ = Новый ТаблицаЗначений;
ТЗ.Колонки.Добавить("Пометка", Новый ОписаниеТипов("Булево"));
ТЗ.Колонки.Добавить("Представление", Новый ОписаниеТипов("Строка"));
ТЗ.Колонки.Добавить("Элемент");
Для Каждого Элемент Из Список Цикл
Стр = ТЗ.Добавить();
Стр.Пометка = Элемент.Пометка;
Стр.Представление = Элемент.Представление;
Стр.Элемент = Элемент;
КонецЦикла;
ТЗ.Сортировать("Пометка Убыв, Представление");
Результат = Новый СписокЗначений;
Для Каждого Стр Из ТЗ Цикл
Результат.Добавить(Стр.Элемент.Значение, Стр.Элемент.Представление,
Стр.Элемент.Пометка, Стр.Элемент.Картинка);
КонецЦикла;
Ложь меньше Истина, поэтому Пометка Убыв ставит отмеченные элементы наверх. Таблица значений работает только на сервере: в клиентском обработчике формы этот код вынесите в серверную процедуру.
Вычисляемый ключ сортировки
Любое своё правило можно свести к ключу, который сортируется обычным способом. Ключ кладём в Значение, исходные данные — в Представление. Пример: строки по длине, а при равной длине — по алфавиту.
Ключи = Новый СписокЗначений;
Для Каждого Текст Из МассивСтрок Цикл
Ключ = Формат(100000 + СтрДлина(Текст), "ЧГ=0") + Текст;
Ключи.Добавить(Ключ, Текст);
КонецЦикла;
Ключи.СортироватьПоЗначению();
// Порядок представлений: от коротких строк к длинным
Прибавка 100000 даёт ключ фиксированной ширины: длина 7 превращается в «100007», длина 12 — в «100012». Строки сравниваются посимвольно, и без выравнивания «12» оказалось бы раньше «7». Параметр ЧГ=0 убирает разделитель групп разрядов, иначе в строке появится пробел.
Как ведут себя разные типы данных
Результат сортировки зависит от того, что лежит в значениях. Здесь больше всего сюрпризов.
| Тип значения | Порядок по возрастанию | Подводный камень |
|---|---|---|
| Число | 5, 12, 30 | Нет |
| Дата | От ранней к поздней | Время тоже учитывается |
| Булево | Сначала Ложь, потом Истина | Нет |
| Строка | Посимвольно, по алфавиту | «Склад 10» встанет раньше «Склад 2» |
| Ссылка | Не по наименованию | Используйте представление или запрос |
| Разные типы в одном списке | Группами по типу | Порядок групп задаёт платформа |
Числа внутри строк
Самая частая жалоба: «Склад 1», «Склад 10», «Склад 2» вместо ожидаемых 1, 2, 10. Строка сравнивается по символам: у «10» второй символ «0», он меньше «2» — значит, «10» раньше.
Два решения:
- Хранить номер числом в
Значение, а текст — вПредставление, и сортировать по значению. - Строить ключ с выравниванием номера, как в примере с длиной строки:
Формат(100000 + Номер, "ЧГ=0").
Ссылки на справочники и документы
Если в список значений добавлены ссылки без представления, СортироватьПоЗначению() не даст алфавитного порядка. Надёжный вариант — заполнить представление явно и сортировать по нему:
Список.Добавить(Склад, Строка(Склад));
// ...
Список.СортироватьПоПредставлению();
Для документов Строка(Ссылка) даёт вид «Поступление (акт, накладная) 00-000123 от 12.03…». По алфавиту такие строки выстроятся по виду документа и номеру, а не по дате. Нужен порядок по дате — берите запрос с УПОРЯДОЧИТЬ ПО Дата.
Смешанные типы
Список, где вперемешку числа, строки и Неопределено, сортируется без ошибки. Но значения сгруппируются по типам, и на порядок групп между собой полагаться не стоит. Если такая смесь возникла случайно — это повод проверить код заполнения, а не подбирать сортировку.
СортироватьПоЗначению() с нужным направлением. Одна строка кода, работает и на клиенте.Представление через Строка(Ссылка) и вызовите СортироватьПоПредставлению(). Для сортировки по другому реквизиту — запрос с условием В (&Список).ЗагрузитьЗначения(), отсортируйте, верните ВыгрузитьЗначения().ТЗ.Сортировать("Колонка", Новый СравнениеЗначений) — тогда ссылки встанут по представлению.УПОРЯДОЧИТЬ ПО.Сортировка больших объёмов: что ускоряет, а что тормозит
Встроенные методы сортировки выполняются внутри платформы, а не построчно во встроенном языке. Поэтому вызов СортироватьПоЗначению() на десятках тысяч элементов отрабатывает быстро, а самописная «пузырьковая» сортировка в цикле на тех же данных заметно подвиснет: число сравнений у неё растёт квадратично.
Что помогает на больших объёмах:
- ⚡ Сортировать в запросе. Если данные пришли из базы,
УПОРЯДОЧИТЬ ПОдешевле, чем выгрузка и сортировка в памяти. Для больших таблиц сортировка по индексированному полю отрабатывает быстрее. - ⚡ Сортировать один раз. Не вызывайте сортировку внутри цикла добавления элементов. Сначала наполните коллекцию, потом отсортируйте.
- ⚡ Сортировать там, где лежат данные. Таблица значений на 50 000 строк, переданная с сервера на клиент ради сортировки, гоняет по сети лишние данные. Отсортируйте на сервере и передайте результат.
- ⚡ Не обращаться к реквизитам через точку в цикле. Код вида
Стр.Номенклатура.Артикулдля каждой строки обращается к базе. Нужен артикул для сортировки — получите его запросом сразу, отдельной колонкой.
Что тормозит чаще всего:
- 🐢 собственная сортировка вставками или «пузырьком» на встроенном языке;
- 🐢 построение ключа сортировки с чтением реквизитов ссылки через точку;
- 🐢 перекладывание данных туда-обратно между списком, массивом и таблицей несколько раз подряд.
Если сортировка нужна только для показа в форме, часто проще отдать её пользователю. Динамический список и таблица формы со списком сортируются щелчком по заголовку колонки, без кода.
Частые вопросы
Сортировка списка значений меняет исходный список или создаёт новый?
Список.Скопировать() до сортировки.Можно ли отсортировать список значений на клиенте?
СортироватьПоЗначению() и СортироватьПоПредставлению() доступны на клиенте. А вот таблица значений и запрос работают только на сервере.Как развернуть список в обратном порядке без сортировки?
Для Сч = 1 По Список.Количество() Цикл Элемент = Список[Список.Количество() - Сч]; .... Индексы начинаются с нуля, поэтому последний элемент — Список[Количество - 1].Почему после сортировки новые элементы снова идут не по порядку?
Добавить() кладёт элемент в конец, Вставить(Индекс, ...) — на указанную позицию. После добавления вызовите сортировку ещё раз.Как убрать дубли и заодно отсортировать?
ТЗ.Свернуть("Значение"), затем ТЗ.Сортировать("Значение") и выгрузите колонку через ВыгрузитьКолонку().