В процессе разработки на платформе 1С:Предприятие часто возникает задача приоритетной сортировки. Представьте ситуацию: у вас есть основная таблица значений с сотнями строк номенклатуры, и есть вторая таблица (или массив), в которой перечислены «важные» позиции. Ваша цель — сделать так, чтобы эти важные позиции оказались в самом верху списка, а все остальные строки следовали за ними. Стандартный метод Сортировать() здесь не поможет, так как он работает только по значениям самих колонок.
В этой статье мы подробно разберем несколько эффективных способов решения этой задачи, начиная от простых запросов и заканчивая оптимизированными программными алгоритмами для больших объемов данных.
Это наиболее универсальный и «чистый» способ, если ваши данные можно поместить во временные таблицы. Основная идея заключается в том, чтобы присоединить к основной таблице вторую и создать виртуальное поле, по которому будет производиться сортировка.
Разберем этот метод по шагам. Допустим, ТЗ_Основная — это ваши данные, а ТЗ_Приоритет — список номенклатуры, которая должна быть сверху.
Проанализируем ситуацию с NULL. При левом соединении для тех строк основной таблицы, которых нет во второй, в колонках второй таблицы появится значение NULL. Проблема в том, что разные СУБД (MSSQL и PostgreSQL) по-разному сортируют NULL: в одном случае они окажутся в начале, в другом — в конце. Чтобы решение было надежным, мы будем использовать конструкцию ВЫБОР.
Рассмотрим пример кода:
// Создадим менеджер временных таблиц
МенеджерВТ = Новый МенеджерВременныхТаблиц;
Запрос = Новый Запрос;
Запрос.МенеджерВременныхТаблиц = МенеджерВТ;
// Помещаем основную таблицу в ВТ
Запрос.Текст = "ВЫБРАТЬ * ПОМЕСТИТЬ ВТ_Основная ИЗ &ТЗ_Основная КАК ТЗ";
Запрос.УстановитьПараметр("ТЗ_Основная", ТЗ_Основная);
Запрос.Выполнить();
// Помещаем таблицу приоритетов в ВТ
Запрос.Текст = "ВЫБРАТЬ Номенклатура ПОМЕСТИТЬ ВТ_Приоритет ИЗ &ТЗ_Приоритет КАК ТЗ";
Запрос.УстановитьПараметр("ТЗ_Приоритет", ТЗ_Приоритет);
Запрос.Выполнить();
// Основной запрос с логикой сортировки
Запрос.Текст =
"ВЫБРАТЬ
| Т1.*,
| ВЫБОР
| КОГДА Т2.Номенклатура ЕСТЬ NULL ТОГДА 1
| ИНАЧЕ 0
| КОНЕЦ КАК ИндексПриоритета
|ИЗ
| ВТ_Основная КАК Т1
| ЛЕВОЕ СОЕДИНЕНИЕ ВТ_Приоритет КАК Т2
| ПО Т1.Номенклатура = Т2.Номенклатура
|УПОРЯДОЧИТЬ ПО
| ИндексПриоритета,
| Т1.Номенклатура";
ТЗ_Результат = Запрос.Выполнить().Выгрузить();
В данном примере мы создали искусственное поле ИндексПриоритета. Если номенклатура найдена во второй таблице, индекс равен 0, если нет — 1. При сортировке по возрастанию все «нулевые» строки (приоритетные) гарантированно поднимутся вверх.
Если вы не хотите использовать механизм запросов (например, из-за небольшого объема данных или специфики работы в коде), можно воспользоваться методом добавления временной колонки прямо в таблицу значений.
Выясним причину, почему этот метод удобен: он позволяет гибко управлять весом каждой строки. Мы добавим колонку Уровень, заполним ее значениями и вызовем стандартную сортировку.
Рассмотрим алгоритм действий:
ТЗ1 колонку с типом Число.ТЗ2 (которая содержит образцы).ТЗ1 и проставляем им высокий приоритет.
// 1. Добавляем колонку для веса
ТЗ1.Колонки.Добавить("Приоритет", Новый ОписаниеТипов("Число"));
// 2. Устанавливаем начальное значение (низкий приоритет) для всех строк
Для Каждого Стр Из ТЗ1 Цикл
Стр.Приоритет = 1;
КонецЦикла;
// 3. Повышаем приоритет для тех, кто есть во второй таблице
// Предположим, во второй таблице (или массиве) список приоритетной номенклатуры
Для Каждого СтрПриоритет Из ТЗ2 Цикл
ПараметрыПоиска = Новый Структура("Номенклатура", СтрПриоритет.Номенклатура);
НайденныеСтроки = ТЗ1.НайтиСтроки(ПараметрыПоиска);
Для Каждого СтрокаТЗ1 Из НайденныеСтроки Цикл
СтрокаТЗ1.Приоритет = 0; // Самый высокий приоритет
КонецЦикла;
КонецЦикла;
// 4. Сортируем и убираем лишнее
ТЗ1.Сортировать("Приоритет Возр, Номенклатура Возр");
ТЗ1.Колонки.Удалить("Приоритет");
Важное дополнение: Если вам нужно, чтобы номенклатуры из второй таблицы шли не просто «кучей» сверху, а именно в том порядке, в котором они перечислены в ТЗ2, вы можете присваивать СтрокаТЗ1.Приоритет значение индекса строки из ТЗ2.
Рассмотрим ситуацию, когда в основной таблице десятки тысяч строк. Метод НайтиСтроки() внутри цикла будет работать медленно, так как он фактически перебирает таблицу заново на каждой итерации (сложность алгоритма близка к O(N*M)).
Чтобы ускорить процесс, воспользуемся объектом Соответствие, который работает по принципу хеш-таблицы и обеспечивает мгновенный поиск.
// Создаем соответствие для быстрого поиска
КартаПриоритетов = Новый Соответствие;
Индекс = 0;
Для Каждого Стр Из ТЗ2 Цикл
// Сохраняем порядковый номер как вес
КартаПриоритетов.Вставить(Стр.Номенклатура, Индекс);
Индекс = Индекс + 1;
КонецЦикла;
// Добавляем колонку
ТЗ1.Колонки.Добавить("Вес", Новый ОписаниеТипов("Число"));
МаксимальноеЧисло = 999999; // Для тех, кого нет в списке приоритетов
// Проходим по основной таблице ОДИН раз
Для Каждого Стр Из ТЗ1 Цикл
ПорядковыйНомер = КартаПриоритетов.Получить(Стр.Номенклатура);
Если ПорядковыйНомер <> Неопределено Тогда
Стр.Вес = ПорядковыйНомер;
Иначе
Стр.Вес = МаксимальноеЧисло;
КонецЕсли;
КонецЦикла;
ТЗ1.Сортировать("Вес Возр");
Этот метод значительно быстрее на больших объемах данных, так как мы совершаем всего один проход по основной таблице.
Иногда проще не сортировать старую таблицу, а собрать новую. Этот метод особенно полезен, если вам нужно строгое соответствие порядку второй таблицы и вы хотите легко отсечь дубли или сгруппировать данные.
Разберем по шагам:
ТЗ_Результат путем копирования структуры исходной таблицы (используем метод Скопировать(Новый Массив()), чтобы получить только структуру без строк).Проанализируем пример реализации:
ТЗ_Результат = ТЗ_Основная.Скопировать(Новый Массив()); // Только колонки
// 1. Сначала переносим приоритетные
Для Каждого СтрОбразец Из ТЗ_Приоритет Цикл
Отбор = Новый Структура("Номенклатура", СтрОбразец.Номенклатура);
Найденные = ТЗ_Основная.НайтиСтроки(Отбор);
Для Каждого ТекСтрока Из Найденные Цикл
ЗаполнитьЗначенияСвойств(ТЗ_Результат.Добавить(), ТекСтрока);
// Можно пометить строку в ТЗ_Основная для последующего удаления
ТекСтрока.Номенклатура = Справочники.Номенклатура.ПустаяСсылка();
КонецЦикла;
КонецЦикла;
// 2. Добавляем всех остальных
Для Каждого Стр Из ТЗ_Основная Цикл
Если НЕ Стр.Номенклатура.Пустая() Тогда
ЗаполнитьЗначенияСвойств(ТЗ_Результат.Добавить(), Стр);
КонецЕсли;
КонецЦикла;
Если ваша номенклатура ведется в разрезе характеристик, важно помнить: во второй таблице может быть указана только Номенклатура, а в первой — Номенклатура + Характеристика. В этом случае при использовании ЛЕВОЕ СОЕДИНЕНИЕ или НайтиСтроки() по одной номенклатуре из «приоритетного» списка поднимется сразу несколько строк с разными характеристиками.
Рекомендация: если вам важен порядок конкретных пар (Номенклатура + Характеристика), обязательно включайте обе колонки в условия поиска или соединения. Если же характеристики не имеют значения для приоритета, достаточно поиска только по ссылке на номенклатуру.
Выбор метода зависит от ваших условий:
Используйте эти подходы, чтобы сделать интерфейс ваших обработок более удобным для пользователя, поднимая наиболее важную информацию в начало списков.