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

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

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

Проанализируем конкретную проблему: имеется обработка с таблицей изменений, содержащей около 20 000 строк. Каждая строка указывает, в каком документе, какой "старый договор" нужно заменить на "новый". Документов при этом значительно меньше, чем строк, то есть в одном документе нужно произвести несколько замен. Наша цель — создать алгоритм, который минимизирует количество обращений к базе данных, а именно — операций получения и записи объектов документов.

Основная идея: Группировка операций по документам

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

Разберем по шагам, как реализовать этот алгоритм.

  1. Подготовка данных. Первое и самое важное действие — отсортировать нашу таблицу изменений по ссылке на документ. Это позволит нам итеративно проходить по строкам, которые относятся к одному и тому же документу, подряд.
  2. Организация цикла. Мы будем использовать один основной цикл для перебора отсортированной таблицы. Внутри цикла нам понадобится переменная для хранения ссылки на обрабатываемый в данный момент документ.
  3. Логика обработки. В каждой итерации цикла мы будем сравнивать ссылку на документ из текущей строки с той, что хранится в нашей переменной.
    • Если ссылки совпадают, это значит, мы все еще работаем с тем же документом. Мы просто вносим очередное изменение в его табличную часть. Ничего не записываем и не проводим!
    • Если ссылки не совпадают, это сигнал о том, что мы закончили со всеми правками для предыдущего документа и перешли к новому. В этот момент мы должны записать или провести предыдущий, уже измененный, документ. После этого мы получаем объект нового документа, вносим в него первое изменение и сохраняем его ссылку в нашу переменную для последующих сравнений.
  4. Завершение. После того как цикл завершит свою работу, у нас останется последний обработанный документ, который еще не был записан. Его необходимо записать отдельно после окончания цикла.

Пример реализации на языке 1С

Давайте посмотрим, как этот алгоритм выглядит в виде кода. Предположим, у нас есть таблица значений ТаблицаИзменений с колонками "Документ" (тип ДокументСсылка), "СтарыйДоговор" и "НовыйДоговор".


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

// Переменная для хранения объекта документа, с которым работаем в данный момент
ТекущийДокументОбъект = Неопределено;

// Шаг 2: Начинаем основной цикл по строкам таблицы изменений
Для Каждого СтрокаТЗ Из ТаблицаИзменений Цикл

    // Шаг 3: Проверяем, перешли ли мы к новому документу
    Если ТекущийДокументОбъект = Неопределено Или ТекущийДокументОбъект.Ссылка <> СтрокаТЗ.Документ Тогда
        
        // Если это не первая итерация цикла, значит, у нас есть предыдущий документ,
        // который нужно записать.
        Если ТекущийДокументОбъект <> Неопределено Тогда
            Попытка
                // Здесь решаем, проводить или просто записывать.
                // Для документов начисления почти всегда нужно перепроведение.
                ТекущийДокументОбъект.Записать(РежимЗаписиДокумента.Проведение);
                Сообщить("Документ " + ТекущийДокументОбъект.Ссылка + " успешно изменен и проведен.");
            Исключение
                Сообщить("Не удалось провести документ " + ТекущийДокументОбъект.Ссылка + ". Ошибка: " + ОписаниеОшибки());
            КонецПопытки;
        КонецЕсли;
        
        // Теперь получаем объект нового документа из текущей строки ТЗ
        Попытка
            ТекущийДокументОбъект = СтрокаТЗ.Документ.ПолучитьОбъект();
            // Сразу же модифицируем объект, чтобы не потерять блокировки и данные
            ТекущийДокументОбъект.Заблокировать();
        Исключение
            Сообщить("Не удалось получить объект для документа " + СтрокаТЗ.Документ + ". Пропускаем. Ошибка: " + ОписаниеОшибки());
            ТекущийДокументОбъект = Неопределено;
            Продолжить; // Переходим к следующей строке ТЗ
        КонецИсключения;

    КонецЕсли;

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

КонецЦикла;

// Шаг 4: Не забываем записать самый последний документ после выхода из цикла
Если ТекущийДокументОбъект <> Неопределено Тогда
    Попытка
        ТекущийДокументОбъект.Записать(РежимЗаписиДокумента.Проведение);
        Сообщить("Последний документ " + ТекущийДокументОбъект.Ссылка + " успешно изменен и проведен.");
    Исключение
        Сообщить("Не удалось провести последний документ " + ТекущийДокументОбъект.Ссылка + ". Ошибка: " + ОписаниеОшибки());
    КонецПопытки;
КонецЕсли;

Этот код выполняет задачу значительно быстрее, так как количество операций ПолучитьОбъект() и Записать() равно количеству уникальных документов, а не общему количеству строк для изменения.

Важный аспект: Запись или Проведение?

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

Альтернативы и стандартные инструменты

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

  1. Групповое изменение реквизитов. Это стандартная обработка, доступная в большинстве конфигураций (обычно в разделе "Администрирование" -> "Обслуживание"). Она позволяет отобрать документы по нужным критериям и массово изменить значения реквизитов, в том числе и в табличных частях. Это первое, что стоит попробовать, так как это может избавить от необходимости программирования.
  2. Поиск и замена значений. Еще один мощный инструмент, который предназначен для глобальной замены одной ссылки на другую во всей информационной базе (например, заменить все вхождения "Старый договор №1" на "Новый договор №1"). Его главное преимущество — скорость и возможность замены без перепроведения документов. Однако использовать его нужно с предельной осторожностью, так как он вносит изменения напрямую в данные, и только после полного понимания последствий. Обязательно сделайте резервную копию перед использованием.
  3. Использование транзакций. При выполнении любых массовых изменений данных крайне рекомендуется оборачивать всю операцию в транзакцию. Это гарантирует атомарность операции: либо все документы будут успешно изменены, либо, если на каком-то из них произойдет ошибка, все ранее сделанные изменения откатятся. Это защищает от ситуации, когда половина данных изменена, а половина нет.
    
    НачатьТранзакцию();
    Попытка
        // ... здесь ваш код обработки документов ...
        ЗафиксироватьТранзакцию();
    Исключение
        ОтменитьТранзакцию();
        Сообщить("Произошла ошибка, все изменения отменены!");
    КонецПопытки;
    

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

← На главную