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

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

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

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

Метод 1: Использование метода НайтиСтроки()

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

Рассмотрим по шагам, как это реализовать в коде:


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

В данном примере мы используем объект Структура для задания критериев поиска. Метод НайтиСтроки() возвращает массив всех подходящих строк. Если размер массива равен нулю, мы смело вызываем Добавить().

Метод 2: Оптимизация через объект Соответствие (Map)

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

Объект Соответствие работает по принципу хеш-таблицы, поэтому поиск значения по ключу в нем происходит практически мгновенно, независимо от объема данных. Разберем этот алгоритм:

  1. Создаем Соответствие.
  2. Одним циклом обходим существующую табличную часть и записываем в Соответствие уникальный ключ (например, комбинацию ID номенклатуры и даты).
  3. В цикле обхода выборки запроса просто проверяем наличие ключа в нашем «индексе».

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


// Создаем уникальный текстовый или составной ключ для проверки
СуществующиеСтроки = Новый Соответствие;

Для Каждого СтрТЧ Из Объект.Продукция Цикл
    // Ключ может быть строкой или массивом/структурой
    Ключ = Строка(СтрТЧ.Номенклатура.УникальныйИдентификатор()) + "_" + Формат(СтрТЧ.ДатаПоступления, "ДФ=yyyyMMddHHmmss");
    СуществующиеСтроки.Вставить(Ключ, Истина);
КонецЦикла;

// Теперь при обходе выборки
Пока Выборка.Следующий() Цикл
    КлючПоиска = Строка(Выборка.Номенклатура.УникальныйИдентификатор()) + "_" + Формат(Выборка.ДатаПоступления, "ДФ=yyyyMMddHHmmss");
    
    Если СуществующиеСтроки[КлючПоиска] = Неопределено Тогда
        НовСтр = Объект.Продукция.Добавить();
        ЗаполнитьЗначенияСвойств(НовСтр, Выборка);
        // Не забываем добавить новую строку в соответствие, чтобы не продублировать ее в этом же цикле
        СуществующиеСтроки.Вставить(КлючПоиска, Истина);
    КонецЕсли;
КонецЦикла;

Метод 3: Исключение дублей на уровне запроса

Наиболее профессиональный подход — не выбирать из базы данных те строки, которые уже есть в документе. Это экономит ресурсы сервера и сокращает объем передаваемых данных. Для этого мы можем выгрузить ключевые поля из табличной части во временную таблицу и выполнить ЛЕВОЕ СОЕДИНЕНИЕ в запросе.

Рассмотрим подробнее этот способ:


&НаСервере
Процедура ЗаполнитьБезДублейЧерезЗапрос()
    
    // 1. Подготавливаем таблицу значений с существующими данными
    ТЗ_Существующие = Объект.Продукция.Выгрузить( , "Номенклатура, ДатаПоступления");
    
    МенеджерВТ = Новый МенеджерВременныхТаблиц;
    Запрос = Новый Запрос;
    Запрос.МенеджерВременныхТаблиц = МенеджерВТ;
    
    // 2. Помещаем существующие данные во временную таблицу
    Запрос.Текст = "ВЫБРАТЬ * ПОМЕСТИТЬ ВТ_ДанныеДокумента ИЗ &ТЗ_Существующие КАК ТЗ_Существующие";
    Запрос.УстановитьПараметр("ТЗ_Существующие", ТЗ_Существующие);
    Запрос.Выполнить();
    
    // 3. Основной запрос с фильтрацией через ЛЕВОЕ СОЕДИНЕНИЕ
    Запрос.Текст = 
        "ВЫБРАТЬ
        |   Регистр.Номенклатура КАК Номенклатура,
        |   Регистр.Период КАК ДатаПоступления,
        |   Регистр.КоличествоПриход КАК Количество
        |ИЗ
        |   РегистрНакопления.ДеталиНаПриемкуНаСклад.ОстаткиИОбороты(&НачалоПериода, &КонецПериода, , , ) КАК Регистр
        |ЛЕВОЕ СОЕДИНЕНИЕ ВТ_ДанныеДокумента КАК ВТ_Док
        |ПО Регистр.Номенклатура = ВТ_Док.Номенклатура
        |   И Регистр.Период = ВТ_Док.ДатаПоступления
        |ГДЕ
        |   ВТ_Док.Номенклатура ЕСТЬ NULL"; // Выбираем только те, которых НЕТ в документе
        
    // ... установка параметров и выполнение запроса ...
    Выборка = Запрос.Выполнить().Выбрать();
    
    Пока Выборка.Следующий() Цикл
        ЗаполнитьЗначенияСвойств(Объект.Продукция.Добавить(), Выборка);
    КонецЦикла;
    
КонецПроцедуры

Важный нюанс: условие ВТ_Док.Номенклатура ЕСТЬ NULL гарантирует, что в выборку попадут только уникальные записи из регистра, отсутствующие в текущей табличной части документа.

Какой метод выбрать?

Подведем итоги нашего анализа:

Также стоит помнить о методе Свернуть(). Если ваша логика позволяет сначала добавить все данные, а потом объединить их, вы можете использовать ТЗ.Свернуть("Номенклатура, ДатаПоступления", "Количество"). Однако будьте осторожны: если пользователь заполнил в строках какие-то специфические комментарии или флаги, при «сворачивании» эти данные могут быть потеряны или перезаписаны первым попавшимся значением.

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

← На главную