При разработке обработок массового изменения данных в 1С:Предприятие начинающие программисты часто сталкиваются с ситуацией, когда код выполняется без ошибок, но данные в документах остаются прежними. В этой статье мы подробно разберем, почему возникает эта проблема, проанализируем типичные ошибки на примере реального кейса с форума и рассмотрим профессиональные методы оптимизации этой задачи согласно стандартам разработки фирмы «1С».
Рассмотрим исходный код, с которым автор темы обратился на форум. Задача проста: найти документы определенного типа по комментарию и, если в табличной части количество товара равно нулю, проставить единицу.
&НаСервере
Процедура ДобавлениеКоличества
Запрос = Новый Запрос;
Запрос.Текст = "ВЫБРАТЬ | Покупки.Ссылка |ИЗ | Документ.Покупки КАК Покупки |ГДЕ | Покупки.Комментарий = &Комментарий";
Запрос.УстановитьПараметр("Комментарий", "Автомат");
Выборка = Запрос.Выполнить.Выбрать;
Пока Выборка.Следующий Цикл
ОбПокупка = Выборка.Ссылка.ПолучитьОбъект;
Для Каждого Строка Из Выборка.Ссылка.ТабличнаяЧасть4 Цикл
Колич = Строка.Количество;
Если Колич > 0 Тогда
Продолжить Иначе
Колич = ("1,000")
КонецЕсли;
КонецЦикла;
Сообщить("Создается " + ОбПокупка);
Сообщить(Колич);
КонецЦикла;
ОбПокупка.Записать;
Сообщить("Создание завершено!");
КонецПроцедуры
В этом небольшом фрагменте кода скрыто сразу несколько серьезных ошибок, из-за которых алгоритм работает некорректно. Разберем их по шагам:
Для Каждого Строка Из Выборка.Ссылка.ТабличнаяЧасть4 обход идет по коллекции ссылки. Ссылка (ДокументСсылка) предназначена только для чтения данных. Даже если мы попытаемся изменить свойства внутри этого цикла, платформа проигнорирует изменения или выдаст ошибку, так как для редактирования данных необходимо использовать полученный объект ОбПокупка.Колич = Строка.Количество; Колич = ("1,000") лишь перезаписывает значение во временной переменной Колич. Реальное значение в строке табличной части документа при этом не меняется. Чтобы изменить значение, необходимо обращаться напрямую к реквизиту строки: Строка.Количество = 1;.ОбПокупка.Записать; находится за пределами цикла Пока Выборка.Следующий(). В результате выполнения этого кода запишется только самый последний документ из выборки (и то без изменений в табличной части из-за первых двух ошибок), а все предыдущие документы вообще не будут изменены."1,000" может привести к непредсказуемому поведению. В 1С работает неявное приведение типов, но использование запятой в качестве разделителя зависит от региональных настроек сервера. Правильно использовать чистое числовое значение: 1 или 1.0.Для того чтобы исправить базовые ошибки и заставить код выполнять свою задачу, нам необходимо переориентировать цикл на работу с объектом, записывать изменения внутри цикла выборки и напрямую обращаться к реквизитам строк табличной части.
Рассмотрим исправленный вариант базового алгоритма:
&НаСервере
Процедура ДобавлениеКоличества()
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Покупки.Ссылка КАК Ссылка
|ИЗ
| Документ.Покупки КАК Покупки
|ГДЕ
| Покупки.Комментарий = &Комментарий";
Запрос.УстановитьПараметр("Комментарий", "Автомат");
Выборка = Запрос.Выполнить.Выбрать();
Пока Выборка.Следующий() Цикл
// Получаем объект документа для изменения
ОбПокупка = Выборка.Ссылка.ПолучитьОбъект();
// Важно: обходим табличную часть ОБЪЕКТА, а не Ссылки
Для Каждого Строка Из ОбПокупка.ТабличнаяЧасть4 Цикл
Если Строка.Количество = 0 Тогда
// Присваиваем числовое значение напрямую реквизиту строки
Строка.Количество = 1;
КонецЕсли;
КонецЦикла;
// Записываем каждый измененный документ внутри цикла
Попытка
ОбПокупка.Записать();
Сообщить("Документ успешно обновлен: " + ОбПокупка);
Исключение
Сообщить("Не удалось записать документ: " + ОбПокупка + ". Ошибка: " + ОписаниеОшибки());
КонецПопытки;
КонецЦикла;
Сообщить("Обработка документов завершена!");
КонецПроцедуры
Хотя приведенный выше код полностью работоспособен, в реальных высоконагруженных или многопользовательских базах данных он может вызвать серьезные проблемы: от сильного снижения производительности до блокировок базы данных. Разберем профессиональный подход к оптимизации подобных задач.
Метод ПолучитьОбъект() — одна из самых тяжелых операций в платформе 1С. Она считывает из базы данных всю информацию о документе, включая все его реквизиты и все табличные части. В исходном коде мы считываем абсолютно все документы с комментарием "Автомат", даже если у них все в порядке с количеством.
Правильным решением будет перенести фильтрацию на уровень СУБД с помощью запроса. Мы должны выбирать только те ссылки документов, у которых в табличной части действительно есть строки с нулевым или незаполненным количеством. Это позволит избежать лишней работы сервера:
Запрос.Текст =
"ВЫБРАТЬ РАЗЛИЧНЫЕ
| ПокупкиТабЧасть.Ссылка КАК Ссылка
|ИЗ
| Документ.Покупки.ТабличнаяЧасть4 КАК ПокупкиТабЧасть
|ГДЕ
| ПокупкиТабЧасть.Ссылка.Комментарий = &Комментарий
| И (ПокупкиТабЧасть.Количество = 0 ИЛИ ПокупкиТабЧасть.Количество ЕСТЬ NULL)";
Использование ключевого слова РАЗЛИЧНЫЕ гарантирует, что каждая ссылка на документ встретится в выборке только один раз, даже если в документе несколько дефектных строк.
Если изменяемый документ уже был проведен в системе (Ссылка.Проведен = Истина), то простая перезапись через метод Записать() может привести к рассинхронизации данных. Документ в базе изменится, но движения по регистрам останутся старыми. Чтобы этого избежать, необходимо программно анализировать статус проведения документа и перезаписывать его в соответствующем режиме:
Если ОбПокупка.Проведен Тогда
// Перепроводим документ для обновления движений в регистрах накопления/бухгалтерии
ОбПокупка.Записать(РежимЗаписиДокумента.Проведение);
Иначе
// Просто сохраняем изменения без проведения
ОбПокупка.Записать(РежимЗаписиДокумента.Запись);
КонецЕсли;
При массовом изменении документов в клиент-серверном варианте работы крайне важно использовать управляемые блокировки. Если этого не сделать, другой пользователь или регламентное задание может попытаться изменить этот же документ одновременно с нами, что вызовет ошибку взаимной блокировки (Deadlock). Перед получением объекта накладываем исключительную блокировку на документ.
Сведем все лучшие практики в один готовый шаблон, который можно использовать в реальных проектах:
&НаСервере
Процедура ВыполнитьМассовоеОбновлениеКоличества()
// Шаг 1. Оптимальный запрос к СУБД
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ РАЗЛИЧНЫЕ
| ПокупкиТабличнаяЧасть4.Ссылка КАК Ссылка
|ИЗ
| Документ.Покупки.ТабличнаяЧасть4 КАК ПокупкиТабличнаяЧасть4
|ГДЕ
| ПокупкиТабличнаяЧасть4.Ссылка.Комментарий = &Комментарий
| И (ПокупкиТабличнаяЧасть4.Количество = 0 ИЛИ ПокупкиТабличнаяЧасть4.Количество ЕСТЬ NULL)";
Запрос.УстановитьПараметр("Комментарий", "Автомат");
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
// Шаг 2. Установка управляемой блокировки на документ
Блокировка = Новый БлокировкаДанных;
ЭлементБлокировки = Блокировка.Добавить("Документ.Покупки");
ЭлементБлокировки.УстановитьПараметр("Ссылка", Выборка.Ссылка);
ЭлементБлокировки.Режим = РежимБлокировкиДанных.Исключительный;
Попытка
Блокировка.Заблокировать();
// Шаг 3. Получение объекта
ОбПокупка = Выборка.Ссылка.ПолучитьОбъект();
// Шаг 4. Модификация табличной части объекта
Для Каждого Строка Из ОбПокупка.ТабличнаяЧасть4 Цикл
Если Строка.Количество = 0 Тогда
Строка.Количество = 1; // Используем строго числовой тип данных
КонецЕсли;
КонецЦикла;
// Шаг 5. Запись документа с учетом его статуса проведения
Если ОбПокупка.Проведен Тогда
ОбПокупка.Записать(РежимЗаписиДокумента.Проведение);
Иначе
ОбПокупка.Записать(РежимЗаписиДокумента.Запись);
КонецЕсли;
Сообщить("Успешно обработан документ: " + ОбПокупка.Ссылка);
Исключение
// Шаг 6. Надежная обработка ошибок без прерывания всего процесса
ЗаписьЖурналаРегистрации("МассовоеИзменениеКоличества",
УровеньЖурналаРегистрации.Ошибка,
Метаданные.Документы.Покупки,
Выборка.Ссылка,
"Ошибка обработки документа: " + ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
Сообщить("Не удалось обработать документ: " + Выборка.Ссылка + ". Подробности в Журнале регистрации.");
КонецПопытки;
КонецЦикла;
Сообщить("Массовая обработка документов успешно завершена!");
КонецПроцедуры
Для администраторов и продвинутых пользователей напомним, что писать программный код для таких задач требуется далеко не всегда. Если в вашей базе развернута типовая конфигурация (например, 1С:Бухгалтерия, 1С:Управление торговлей или 1С:ERP), то в них встроен стандартный механизм для массового изменения реквизитов.
Рассмотрим, как решить эту задачу без программирования:
Покупки.Комментарий Равно "Автомат".Табличная часть 4).Количество, установите галочку активности, выберите действие «Установить значение» или используйте формулу для замены нулевых значений на единицу.