Работа с регистрами бухгалтерии в крупных базах данных (например, 1С:УПП или 1С:ERP) часто сопряжена с необходимостью ретроспективного изменения данных. Типичная ситуация: добавлено новое измерение, которое нужно заполнить за прошлые периоды (начиная с 2008 года и далее). В этой статье мы разберем, как правильно организовать процесс массовой правки проводок, избежав при этом критического падения производительности и блокировок системы.
Часто начинающие разработчики пытаются подойти к задаче «в лоб»: прочитать все записи регистра и перебрать их в цикле. Однако в платформе 1С объект РегистрБухгалтерииНаборЗаписей имеет существенное ограничение — он не может функционировать без установленного фильтра по конкретному регистратору. Если вы попробуете выполнить метод Прочитать() без отбора, система выдаст ошибку или вернет пустой результат.
Давайте проанализируем ситуацию: если в базе данных десятки тысяч документов за 15 лет, перебор всех типов документов и поиск среди них тех, что сделали проводки, займет слишком много времени. Нам нужен метод, который позволит точечно работать только с теми данными, которые действительно требуют изменений.
Вместо того чтобы перебирать все виды документов в конфигурации, мы воспользуемся запросом к самой таблице регистра. Это позволит нам получить список только тех регистраторов, которые создали записи в нужном нам регистре (например, «Управленческий»).
Рассмотрим пример запроса:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ РАЗЛИЧНЫЕ
| Управленческий.Регистратор КАК Регистратор
|ИЗ
| РегистрБухгалтерии.Управленческий КАК Управленческий
|ГДЕ
| Управленческий.Период МЕЖДУ &ДатаНачала И &ДатаКонца";
Запрос.УстановитьПараметр("ДатаНачала", ДатаНачала);
Запрос.УстановитьПараметр("ДатаКонца", ДатаКонца);
РезультатЗапроса = Запрос.Выполнить();
ВыборкаРегистратор = РезультатЗапроса.Выбрать();
Использование ключевого слова ВЫБРАТЬ РАЗЛИЧНЫЕ критически важно, так как один документ может порождать десятки проводок, а нам нужна ссылка на него один раз для формирования набора записей.
Существует распространенная ошибка — получение объекта документа через ПолучитьОбъект() для обращения к его движениям. Это крайне медленный способ, так как при вызове ПолучитьОбъект() система инициализирует весь модуль документа, проверяет права доступа и выполняет множество фоновых операций.
Выясним причину, почему работа напрямую через РегистрыБухгалтерии...СоздатьНаборЗаписей() эффективнее: мы работаем непосредственно с таблицей движений, минуя логику самого документа. Проанализируем правильный алгоритм обработки в цикле:
Пока ВыборкаРегистратор.Следующий() Цикл
// Создаем набор записей для конкретного документа
НаборПроводок = РегистрыБухгалтерии.Управленческий.СоздатьНаборЗаписей();
НаборПроводок.Отбор.Регистратор.Установить(ВыборкаРегистратор.Регистратор);
// Считываем записи из базы данных
НаборПроводок.Прочитать();
Для Каждого Проводка Из НаборПроводок Цикл
// Здесь мы заполняем новое измерение или меняем счета
Проводка.НовоеИзмерение = ЗначениеИзмерения;
// Пример логики из практики:
Если Проводка.СчетДт = ПланыСчетов.Управленческий.СырьеИМатериалы Тогда
Проводка.СчетДт = ПланыСчетов.Управленческий.ТоварыПродукция;
КонецЕсли;
КонецЦикла;
// Записываем изменения, замещая старые записи
НаборПроводок.Записать(Истина);
КонецЦикла;
Параметр Записать(Истина) означает, что текущий набор записей в памяти полностью заменит те записи, которые числятся за этим регистратором в базе. Это штатный и самый быстрый способ обновления проводок.
При массовом изменении проводок за длительный период система 1С после каждой записи набора будет пытаться пересчитать итоги (остатки и обороты). Если записей миллионы, это превратит обработку в бесконечный процесс.
Разберем, как этого избежать. Перед запуском основного цикла обработки рекомендуется временно отключить использование итогов для регистра. Рассмотрим последовательность действий:
РегистрыБухгалтерии.Управленческий.УстановитьИспользованиеИтогов(Ложь);РегистрыБухгалтерии.Управленческий.УстановитьИспользованиеИтогов(Истина);РегистрыБухгалтерии.Управленческий.ПересчитатьИтоги();Важно: Этот метод подходит только в том случае, если во время работы обработки в базе не работают другие пользователи, так как остатки по счетам будут временно недоступны или неактуальны.
Для обеспечения стабильности работы системы и предотвращения переполнения лога транзакций SQL-сервера, рекомендуется фиксировать транзакцию не после каждого документа и не один раз в конце, а пакетами (например, по 1000 документов).
Посмотрим на пример реализации пакетной обработки:
Счетчик = 0;
НачатьТранзакцию();
Пока ВыборкаРегистратор.Следующий() Цикл
// ... код обработки набора записей ...
Счетчик = Счетчик + 1;
Если Счетчик % 1000 = 0 Тогда
ЗафиксироватьТранзакцию();
НачатьТранзакцию();
КонецЕсли;
КонецЦикла;
Если ТранзакцияАктивна() Тогда
ЗафиксироватьТранзакцию();
КонецЕсли;
Проанализируем некоторые заблуждения, встречающиеся на форумах:
МенеджерЗаписи не существует. Он предназначен только для регистров сведений. Работа с проводками всегда ведется через НаборЗаписей.UPDATE в таблице _AccReg..., это категорически не рекомендуется. Такое действие нарушает целостность данных 1С, не обновляет итоги и может привести к непредсказуемым ошибкам в работе платформы.Таким образом, мы выяснили, что наиболее эффективным и безопасным способом изменения проводок является сочетание точного запроса к регистраторам и использование объекта НаборЗаписей с периодической фиксацией транзакций.