Как сравнить две таблицы значений в 1С и найти добавленные или удаленные строки

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

В процессе разработки часто возникает задача сравнения двух списков. Классический пример: на форме есть две таблицы, и пользователь перемещает элементы из одной в другую (Drag-and-Drop). В итоге нам нужно понять, кто из сотрудников был добавлен в итоговый список, а кто — исключен из него, чтобы корректно отразить эти изменения в документе-регистраторе.

Разберем подробно, как реализовать этот механизм, используя разные подходы: от простого перебора до оптимизированных методов с использованием запросов и статусных моделей.

Этап 1. Фиксация исходного состояния

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

Рассмотрим вариант хранения исходной таблицы в реквизите формы. В процедуре ПриСозданииНаСервере или ПриОткрытииНаСервере заполним вспомогательную таблицу значений:


&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
    
    // Загружаем текущий состав группы из базы данных
    ТЗ_Эталон = ПолучитьТекущийСоставГруппыИзБазы(); 
    
    // Копируем данные в реквизит формы (скрытую таблицу), 
    // чтобы иметь доступ к ним при нажатии кнопки Сформировать
    ЗначениеВДанныеФормы(ТЗ_Эталон, Объект.СписокГруппыЭталон);
    
КонецПроцедуры

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

Этап 2. Алгоритм сравнения через поиск строк

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

Выполним следующие шаги:

  1. Копируем «НовыйСписок» в итоговую таблицу. Всем сотрудникам здесь ставим признак «В группе».
  2. Обходим циклом «СтарыйСписок».
  3. Для каждого сотрудника из старого списка ищем его в новом списке с помощью метода НайтиСтроки().
  4. Если сотрудник не найден — значит, он был удален. Добавляем его в итоговую таблицу с признаком «Исключен».

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


&НаСервере
Функция СформироватьДокументНаСервере()
    
    // Преобразуем данные формы в реальные таблицы значений
    ТЗ_НовыйСписок = ДанныеФормыВЗначение(Объект.СписокГруппы, Тип("ТаблицаЗначений"));
    ТЗ_СтарыйСписок = ДанныеФормыВЗначение(Объект.СписокГруппыЭталон, Тип("ТаблицаЗначений"));
    
    // Подготовим таблицу для документа
    ТЗ_Результат = ТЗ_НовыйСписок.Скопировать();
    ТЗ_Результат.Колонки.Добавить("ВГруппе", Новый ОписаниеТипов("Булево"));
    
    // Все, кто сейчас в списке — активны
    Для Каждого Стр Из ТЗ_Результат Цикл
        Стр.ВГруппе = Истина;
    КонецЦикла;
    
    // Ищем тех, кого удалили
    ПараметрыПоиска = Новый Структура;
    Для Каждого Стр_Старый Из ТЗ_СтарыйСписок Цикл
        ПараметрыПоиска.Вставить("Сотрудник", Стр_Старый.Сотрудник);
        НайденныеСтроки = ТЗ_НовыйСписок.НайтиСтроки(ПараметрыПоиска);
        
        Если НайденныеСтроки.Количество() = 0 Тогда
            // Сотрудника нет в новом списке — он исключен
            НоваяСтр = ТЗ_Результат.Добавить();
            ЗаполнитьЗначенияСвойств(НоваяСтр, Стр_Старый);
            НоваяСтр.ВГруппе = Ложь;
        КонецЕсли;
    КонецЦикла;
    
    // Теперь ТЗ_Результат содержит и добавленных, и удаленных сотрудников
    Возврат СоздатьДокументПоТаблице(ТЗ_Результат);
    
КонецФункции

Этап 3. Оптимизация через использование объекта «Соответствие»

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

Рассмотрим, как изменить алгоритм:

  1. Выгружаем сотрудников «НовогоСписка» в Соответствие, где ключ — это ссылка на сотрудника.
  2. При обходе «СтарогоСписка» проверяем наличие ключа в соответствии методом Получить().

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

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

Этап 4. Метод «Виртуального удаления» (лучшая практика UX)

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

Вместо того чтобы перемещать строки между двумя таблицами, мы можем добавить в одну основную таблицу колонку Статус (Добавлен, Удален, Без изменений). При «удалении» сотрудника мы не удаляем строку, а меняем статус на «Удален» и используем Условное оформление, чтобы визуально выделить эту строку (например, серым цветом или зачеркиванием).

Преимущества этого метода:

Разберем реализацию перетаскивания при таком подходе. В событии СписокГруппыПеретаскивание мы не удаляем объект, а обновляем его реквизит:


&НаКлиенте
Процедура СписокГруппыПеретаскивание(Элемент, ПараметрыПеретаскивания, СтандартнаяОбработка, Строка, Поле)
    
    СтандартнаяОбработка = Ложь;
    СотрудникСсылка = ПараметрыПеретаскивания.Значение;
    
    // Ищем, есть ли уже такой сотрудник в нашей таблице
    // Если есть и был помечен как "Удален" — меняем статус на "Активен"
    // Если нет — добавляем новую строку со статусом "Добавлен"
    ОбновитьСтатусСотрудникаНаСервере(СотрудникСсылка);
    
КонецПроцедуры

Этап 5. Сравнение с помощью запроса

Для максимально быстрой обработки больших объемов данных на сервере целесообразно использовать механизм временных таблиц. Выясним, как это работает. Мы можем передать обе таблицы (старую и новую) в запрос и использовать оператор ОБЪЕДИНИТЬ ВСЕ с последующей группировкой или оператор ИСКЛЮЧАЯ.

Пример логики запроса для поиска исключенных:


ВЫБРАТЬ Сотрудник ИЗ &ТЗ_Старая КАК ТЗ_Старая
ИСКЛЮЧАЯ
ВЫБРАТЬ Сотрудник ИЗ &ТЗ_Новая КАК ТЗ_Новая

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

Резюме

Выбор метода зависит от сложности вашей задачи:

Помните, что при работе на управляемых формах всегда нужно преобразовывать данные формы в значения (ДанныеФормыВЗначение) перед выполнением алгоритмов сравнения на сервере.

← На главную