Как отсортировать одну табличную часть на основании другой в 1С?

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

В практике разработки на платформе 1С часто возникает задача, когда стандартной сортировки (по возрастанию или убыванию) недостаточно. Представьте ситуацию: у вас есть две таблицы (например, ДанныеФормаКоллекция или стандартные табличные части), содержащие одинаковое связующее поле, например, Имя или Номенклатура. Нам необходимо упорядочить первую таблицу ровно в том же порядке (по образу и подобию), в котором расположены элементы во второй, эталонной таблице. Этот порядок может быть абсолютно произвольным и не поддаваться классическим алгоритмам сортировки по алфавиту.

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


Метод 1. Сортировка через запрос с использованием временных таблиц

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

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

Посмотрим на пример программного кода:


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

Таблица1 = ТаблицаИсточник.Выгрузить();
Таблица2 = ТаблицаЭталон.Выгрузить();

// Добавим в эталонную таблицу колонку "НомерСтрокиЭталона", чтобы зафиксировать порядок
Таблица2.Колонки.Добавить("НомерСтрокиЭталона", Новый ОписаниеТипов("Число"));
Для Индекс = 0 По Таблица2.Количество() - 1 Цикл
    Таблица2[Индекс].НомерСтрокиЭталона = Индекс;
КонецЦикла;

Запрос = Новый Запрос;
Запрос.УстановитьПараметр("ТЗ_Источник", Таблица1);
Запрос.УстановитьПараметр("ТЗ_Эталон", Таблица2);

Запрос.Текст = 
"ВЫБРАТЬ
|	ТЗ.Имя КАК Имя,
|	ТЗ.Количество КАК Количество
|ПОМЕСТИТЬ ВТ_Источник
|ИЗ
|	&ТЗ_Источник КАК ТЗ
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
|	ТЗ_Эт.Имя КАК Имя,
|	ТЗ_Эт.НомерСтрокиЭталона КАК НомерСтрокиЭталона
|ПОМЕСТИТЬ ВТ_Эталон
|ИЗ
|	&ТЗ_Эталон КАК ТЗ_Эт
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
|	ВТ_Источник.Имя КАК Имя,
|	ВТ_Источник.Количество КАК Количество
|ИЗ
|	ВТ_Источник КАК ВТ_Источник
|	ЛЕВОЕ СОЕДИНЕНИЕ ВТ_Эталон КАК ВТ_Эталон
|	ПО ВТ_Источник.Имя = ВТ_Эталон.Имя
|
|УПОРЯДОЧИТЬ ПО
|	ЕСТЬNULL(ВТ_Эталон.НомерСтрокиЭталона, 999999) ВОЗР";

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

Важные детали реализации:

  1. Мы использовали ЛЕВОЕ СОЕДИНЕНИЕ, чтобы гарантировать, что все строки из нашей исходной таблицы останутся на месте, даже если какого-то элемента нет в эталоне.
  2. Для упорядочивания мы применили конструкцию ЕСТЬNULL(ВТ_Эталон.НомерСтрокиЭталона, 999999). Это критически важно, так как разные СУБД (например, PostgreSQL и MS SQL Server) по-разному обрабатывают сортировку значений NULL. Данная конструкция гарантирует, что все «новые» или ненайденные в эталоне строки будут предсказуемо помещены в самый конец результирующего списка.

Метод 2. Быстрая сортировка в памяти через объект "Соответствие" (Рекомендуемый)

Передача данных в запрос требует серверного вызова и сериализации таблиц. Если нам нужно выполнить сортировку «на лету» (особенно на стороне клиента или в высоконагруженных процедурах), лучше использовать возможности встроенного языка 1С.

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

Для достижения максимального быстродействия со сложностью $O(N)$ мы применим объект Соответствие, поиск по которому в 1С выполняется за константное время. Разберем этот алгоритм по шагам:


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

// Шаг 2. Добавляем временную колонку для сортировки в целевую таблицу
ТаблицаИсточник.Колонки.Добавить("ИндексСортировки", Новый ОписаниеТипов("Число"));

// Шаг 3. Заполняем временную колонку индексами из соответствия
Для Каждого СтрокаИсточника Из ТаблицаИсточник Цикл
    НайденныйИндекс = КартаИндексов.Получить(СтрокаИсточника.Имя);
    Если НайденныйИндекс <> Неопределено Тогда
        СтрокаИсточника.ИндексСортировки = НайденныйИндекс;
    Иначе
        // Если элемента нет в эталоне, отправляем его в конец списка
        СтрокаИсточника.ИндексСортировки = 999999;
    КонецЕсли;
КонецЦикла;

// Шаг 4. Сортируем таблицу встроенным оптимизированным методом платформы
ТаблицаИсточник.Сортировать("ИндексСортировки Возр");

// Шаг 5. Удаляем вспомогательную колонку, чтобы вернуть структуру таблицы к исходному виду
ТаблицаИсточник.Колонки.Удалить("ИндексСортировки");

Этот способ работает невероятно быстро, так как встроенный метод Сортировать реализован разработчиками платформы на уровне C++ и оптимизирован для больших объемов данных.


Метод 3. Перезаполнение коллекции на основе копии

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

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

Проанализируем пример кода для этого сценария:


// Создаем копию таблицы с данными
ТаблицаКопия = ТаблицаИсточник.Скопировать();
ТаблицаИсточник.Очистить();

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

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

Преимущества метода: Простота логики и отсутствие необходимости менять структуру колонок.

Недостатки: Низкая производительность на больших таблицах из-за многократного вызова методов поиска и удаления строк.


Важные особенности при работе с управляемыми формами (UI/UX)

Если ваша таблица привязана к реквизиту формы (имеет тип ДанныеФормаКоллекция) и отображается на экране пользователя, применение методов полной очистки (Очистить()) или перезагрузки данных приведет к неприятным побочным эффектам:

Чтобы избежать этих проблем и сохранить комфорт пользователя, перед сортировкой обязательно сохраняйте текущую позицию курсора, а после сортировки — восстанавливайте её. Рассмотрим, как это сделать:


// Сохраняем текущую активную строку на форме
ИмяТаблицыНаФорме = "ИмяТаблицыФормы";
ТекущаяСтрокаИД = Элементы[ИмяТаблицыНаФорме].ТекущаяСтрока;

ЗначениеКлючаАктивнойСтроки = Неопределено;
Если ТекущаяСтрокаИД <> Неопределено Тогда
    АктивнаяСтрока = Объект.ИмяТабличнойЧасти.НайтиПоИдентификатору(ТекущаяСтрокаИД);
    Если АктивнаяСтрока <> Неопределено Тогда
        // Запоминаем уникальное значение, по которому найдем строку после сортировки
        ЗначениеКлючаАктивнойСтроки = АктивнаяСтрока.Имя;
    КонецЕсли;
КонецЕсли;

// --- ВЫПОЛНЯЕМ ЛЮБОЙ ИЗ ВЫБРАННЫХ МЕТОДОВ СОРТИРОВКИ ---

// Восстанавливаем позицию курсора на форме
Если ЗначениеКлючаАктивнойСтроки <> Неопределено Тогда
    Для Каждого СтрокаКоллекции Из Объект.ИмяТабличнойЧасти Цикл
        Если СтрокаКоллекции.Имя = ЗначениеКлючаАктивнойСтроки Тогда
            Элементы[ИмяТаблицыНаФорме].ТекущаяСтрока = СтрокаКоллекции.ПолучитьИдентификатор();
            Прервать;
        КонецЕсли;
    КонецЦикла;
КонецЕсли;

Резюме

Мы подробно разобрали три различных способа упорядочивания табличных частей на основании эталонных списков. Выбор конкретного метода зависит от архитектуры вашей задачи:

Не забывайте заботиться о пользователях системы и сохранять активную строку при программной сортировке таблиц на управляемых формах.

← На главную