Как отсортировать одну таблицу значений на основании порядка или наличия элементов в другой таблице или массиве

Программист 1С v8.3 (Управляемые формы)
← На главную

В процессе разработки на платформе 1С:Предприятие часто возникает задача приоритетной сортировки. Представьте ситуацию: у вас есть основная таблица значений с сотнями строк номенклатуры, и есть вторая таблица (или массив), в которой перечислены «важные» позиции. Ваша цель — сделать так, чтобы эти важные позиции оказались в самом верху списка, а все остальные строки следовали за ними. Стандартный метод Сортировать() здесь не поможет, так как он работает только по значениям самих колонок.

В этой статье мы подробно разберем несколько эффективных способов решения этой задачи, начиная от простых запросов и заканчивая оптимизированными программными алгоритмами для больших объемов данных.

Метод 1. Использование запроса и левого соединения

Это наиболее универсальный и «чистый» способ, если ваши данные можно поместить во временные таблицы. Основная идея заключается в том, чтобы присоединить к основной таблице вторую и создать виртуальное поле, по которому будет производиться сортировка.

Разберем этот метод по шагам. Допустим, ТЗ_Основная — это ваши данные, а ТЗ_Приоритет — список номенклатуры, которая должна быть сверху.

Проанализируем ситуацию с NULL. При левом соединении для тех строк основной таблицы, которых нет во второй, в колонках второй таблицы появится значение NULL. Проблема в том, что разные СУБД (MSSQL и PostgreSQL) по-разному сортируют NULL: в одном случае они окажутся в начале, в другом — в конце. Чтобы решение было надежным, мы будем использовать конструкцию ВЫБОР.

Рассмотрим пример кода:


// Создадим менеджер временных таблиц
МенеджерВТ = Новый МенеджерВременныхТаблиц;
Запрос = Новый Запрос;
Запрос.МенеджерВременныхТаблиц = МенеджерВТ;

// Помещаем основную таблицу в ВТ
Запрос.Текст = "ВЫБРАТЬ * ПОМЕСТИТЬ ВТ_Основная ИЗ &ТЗ_Основная КАК ТЗ";
Запрос.УстановитьПараметр("ТЗ_Основная", ТЗ_Основная);
Запрос.Выполнить();

// Помещаем таблицу приоритетов в ВТ
Запрос.Текст = "ВЫБРАТЬ Номенклатура ПОМЕСТИТЬ ВТ_Приоритет ИЗ &ТЗ_Приоритет КАК ТЗ";
Запрос.УстановитьПараметр("ТЗ_Приоритет", ТЗ_Приоритет);
Запрос.Выполнить();

// Основной запрос с логикой сортировки
Запрос.Текст = 
"ВЫБРАТЬ
|	Т1.*,
|	ВЫБОР
|		КОГДА Т2.Номенклатура ЕСТЬ NULL ТОГДА 1
|		ИНАЧЕ 0
|	КОНЕЦ КАК ИндексПриоритета
|ИЗ
|	ВТ_Основная КАК Т1
|		ЛЕВОЕ СОЕДИНЕНИЕ ВТ_Приоритет КАК Т2
|		ПО Т1.Номенклатура = Т2.Номенклатура
|УПОРЯДОЧИТЬ ПО
|	ИндексПриоритета,
|	Т1.Номенклатура";

ТЗ_Результат = Запрос.Выполнить().Выгрузить();

В данном примере мы создали искусственное поле ИндексПриоритета. Если номенклатура найдена во второй таблице, индекс равен 0, если нет — 1. При сортировке по возрастанию все «нулевые» строки (приоритетные) гарантированно поднимутся вверх.

Метод 2. Программный способ с добавлением вспомогательной колонки

Если вы не хотите использовать механизм запросов (например, из-за небольшого объема данных или специфики работы в коде), можно воспользоваться методом добавления временной колонки прямо в таблицу значений.

Выясним причину, почему этот метод удобен: он позволяет гибко управлять весом каждой строки. Мы добавим колонку Уровень, заполним ее значениями и вызовем стандартную сортировку.

Рассмотрим алгоритм действий:

  1. Добавляем в основную таблицу ТЗ1 колонку с типом Число.
  2. Проходим циклом по второй таблице ТЗ2 (которая содержит образцы).
  3. Для каждого элемента ищем соответствующие строки в ТЗ1 и проставляем им высокий приоритет.
  4. Выполняем сортировку и удаляем временную колонку.

// 1. Добавляем колонку для веса
ТЗ1.Колонки.Добавить("Приоритет", Новый ОписаниеТипов("Число"));

// 2. Устанавливаем начальное значение (низкий приоритет) для всех строк
Для Каждого Стр Из ТЗ1 Цикл
    Стр.Приоритет = 1;
КонецЦикла;

// 3. Повышаем приоритет для тех, кто есть во второй таблице
// Предположим, во второй таблице (или массиве) список приоритетной номенклатуры
Для Каждого СтрПриоритет Из ТЗ2 Цикл
    ПараметрыПоиска = Новый Структура("Номенклатура", СтрПриоритет.Номенклатура);
    НайденныеСтроки = ТЗ1.НайтиСтроки(ПараметрыПоиска);
    
    Для Каждого СтрокаТЗ1 Из НайденныеСтроки Цикл
        СтрокаТЗ1.Приоритет = 0; // Самый высокий приоритет
    КонецЦикла;
КонецЦикла;

// 4. Сортируем и убираем лишнее
ТЗ1.Сортировать("Приоритет Возр, Номенклатура Возр");
ТЗ1.Колонки.Удалить("Приоритет");

Важное дополнение: Если вам нужно, чтобы номенклатуры из второй таблицы шли не просто «кучей» сверху, а именно в том порядке, в котором они перечислены в ТЗ2, вы можете присваивать СтрокаТЗ1.Приоритет значение индекса строки из ТЗ2.

Метод 3. Оптимизация через объект «Соответствие»

Рассмотрим ситуацию, когда в основной таблице десятки тысяч строк. Метод НайтиСтроки() внутри цикла будет работать медленно, так как он фактически перебирает таблицу заново на каждой итерации (сложность алгоритма близка к O(N*M)).

Чтобы ускорить процесс, воспользуемся объектом Соответствие, который работает по принципу хеш-таблицы и обеспечивает мгновенный поиск.


// Создаем соответствие для быстрого поиска
КартаПриоритетов = Новый Соответствие;
Индекс = 0;
Для Каждого Стр Из ТЗ2 Цикл
    // Сохраняем порядковый номер как вес
    КартаПриоритетов.Вставить(Стр.Номенклатура, Индекс);
    Индекс = Индекс + 1;
КонецЦикла;

// Добавляем колонку
ТЗ1.Колонки.Добавить("Вес", Новый ОписаниеТипов("Число"));

МаксимальноеЧисло = 999999; // Для тех, кого нет в списке приоритетов

// Проходим по основной таблице ОДИН раз
Для Каждого Стр Из ТЗ1 Цикл
    ПорядковыйНомер = КартаПриоритетов.Получить(Стр.Номенклатура);
    Если ПорядковыйНомер <> Неопределено Тогда
        Стр.Вес = ПорядковыйНомер;
    Иначе
        Стр.Вес = МаксимальноеЧисло;
    КонецЕсли;
КонецЦикла;

ТЗ1.Сортировать("Вес Возр");

Этот метод значительно быстрее на больших объемах данных, так как мы совершаем всего один проход по основной таблице.

Метод 4. Формирование новой таблицы (Метод «Двух копий»)

Иногда проще не сортировать старую таблицу, а собрать новую. Этот метод особенно полезен, если вам нужно строгое соответствие порядку второй таблицы и вы хотите легко отсечь дубли или сгруппировать данные.

Разберем по шагам:

  1. Создаем ТЗ_Результат путем копирования структуры исходной таблицы (используем метод Скопировать(Новый Массив()), чтобы получить только структуру без строк).
  2. Перебираем эталонную таблицу (где задан порядок). Для каждого элемента находим все вхождения в исходной таблице и переносим их в результат.
  3. Помечаем перенесенные строки в исходной таблице или удаляем их.
  4. В конце добавляем в результат все оставшиеся строки из исходной таблицы.

Проанализируем пример реализации:


ТЗ_Результат = ТЗ_Основная.Скопировать(Новый Массив()); // Только колонки

// 1. Сначала переносим приоритетные
Для Каждого СтрОбразец Из ТЗ_Приоритет Цикл
    Отбор = Новый Структура("Номенклатура", СтрОбразец.Номенклатура);
    Найденные = ТЗ_Основная.НайтиСтроки(Отбор);
    Для Каждого ТекСтрока Из Найденные Цикл
        ЗаполнитьЗначенияСвойств(ТЗ_Результат.Добавить(), ТекСтрока);
        // Можно пометить строку в ТЗ_Основная для последующего удаления
        ТекСтрока.Номенклатура = Справочники.Номенклатура.ПустаяСсылка(); 
    КонецЦикла;
КонецЦикла;

// 2. Добавляем всех остальных
Для Каждого Стр Из ТЗ_Основная Цикл
    Если НЕ Стр.Номенклатура.Пустая() Тогда
        ЗаполнитьЗначенияСвойств(ТЗ_Результат.Добавить(), Стр);
    КонецЕсли;
КонецЦикла;

Нюансы работы с характеристиками

Если ваша номенклатура ведется в разрезе характеристик, важно помнить: во второй таблице может быть указана только Номенклатура, а в первой — Номенклатура + Характеристика. В этом случае при использовании ЛЕВОЕ СОЕДИНЕНИЕ или НайтиСтроки() по одной номенклатуре из «приоритетного» списка поднимется сразу несколько строк с разными характеристиками.

Рекомендация: если вам важен порядок конкретных пар (Номенклатура + Характеристика), обязательно включайте обе колонки в условия поиска или соединения. Если же характеристики не имеют значения для приоритета, достаточно поиска только по ссылке на номенклатуру.

Резюме

Выбор метода зависит от ваших условий:

Используйте эти подходы, чтобы сделать интерфейс ваших обработок более удобным для пользователя, поднимая наиболее важную информацию в начало списков.

← На главную