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