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