Как правильно сопоставить данные запроса с табличной частью и записать изменения в регистр сведений

Программист 1С v8.3 (Обычные формы) Управленческий учет Торговля и дистрибуция
← На главную

При разработке механизмов логирования изменений в 1С программисты часто сталкиваются с ситуацией, когда данные в регистр записываются некорректно: либо значения дублируются, либо записывается только последняя строка, либо вовсе попадает не та номенклатура. Проанализируем типичную ситуацию, когда при изменении количества в документе (например, ВозвратТоваровОтПокупателя или РеализацияТоваровУслуг) необходимо зафиксировать в регистре сведений «было», «стало», а также текущие остатки и резервы.

Разбор основных ошибок в логике обхода данных

Рассмотрим код, который часто приводит к ошибкам. Первая и самая критичная ошибка — неправильная работа с объектом ВыборкаИзРезультатаЗапроса. Посмотрим на этот фрагмент:


Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
    // Здесь пусто
КонецЦикла;

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

Вторая ошибка заключается в попытке заполнить таблицу значений в цикле, не позиционируясь на нужной строке выборки:


Для Каждого Запись Из Форма.ТабТовары Цикл
    Запись.Остаток = Выборка.Остаток; // Ошибка: всегда будет одно и то же значение
    Запись.Резерв = Выборка.Резерв;
КонецЦикла;

Проанализируем ситуацию: здесь цикл идет по строкам таблицы формы, но Выборка никак не «двигается» и не ищет подходящую номенклатуру. Программа просто берет то значение, на котором «застыла» выборка после первого пустого цикла.

Правильный алгоритм: сопоставление через поиск в таблице значений

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

  1. Выполним запрос и выгрузим его в ТаблицаЗначений.
  2. Установим индекс по колонке Номенклатура для моментального поиска.
  3. Переберем строки документа и для каждой строки найдем соответствующие данные в таблице остатков.

Посмотрим на пример реализации этого алгоритма:


// 1. Получаем данные запросом
РезультатЗапроса = Запрос.Выполнить();
ТЗ_Остатки = РезультатЗапроса.Выгрузить();

// 2. Индексируем для ускорения поиска
ТЗ_Остатки.Индексы.Добавить("Номенклатура");

// 3. Обходим таблицу формы (или документа)
Для Каждого СтрокаТЧ Из Форма.ТабТовары Цикл
    
    // Ищем строку в остатках по номенклатуре
    СтруктураПоиска = Новый Структура("Номенклатура", СтрокаТЧ.Номенклатура);
    НайденнаяСтрока = ТЗ_Остатки.НайтиСтроки(СтруктураПоиска);
    
    Если НайденнаяСтрока.Количество() > 0 Тогда
        СтрокаТЧ.Остаток = НайденнаяСтрока[0].Остаток;
        СтрокаТЧ.Резерв  = НайденнаяСтрока[0].Резерв;
    КонецЕсли;
    
КонецЦикла;

Оптимизация записи в Регистр Сведений

Еще одна серьезная проблема в исходном коде — создание и запись набора записей регистра внутри цикла. Выясним причину, почему это плохо: каждое выполнение метода Записать() — это отдельная транзакция и отдельное обращение к серверу базы данных. Если в документе 100 строк, вы сделаете 100 обращений вместо одного.

Правильный подход: создаем один объект НаборЗаписей, в цикле наполняем его строками с помощью метода Добавить(), и только в самом конце, после завершения цикла, вызываем Записать().

Проанализируем исправленный код записи лога:


// Создаем набор ОДИН раз вне цикла
НаборЗаписей = РегистрыСведений.ЛогРедактированияДокументов.СоздатьНаборЗаписей();
// Устанавливаем отбор, если нужно перезаписывать логи только по этому документу
НаборЗаписей.Отбор.Документ.Установить(Ссылка);

Для Каждого СтрокаТоп Из Топ Цикл
    
    // Проверяем, изменилось ли количество
    Если СтрокаТоп.Количество <> СтрокаТоп.КоличествоБыло Тогда
        
        // Добавляем новую запись в НАБОР
        НоваяЗапись = НаборЗаписей.Добавить();
        НоваяЗапись.Период = ТекущаяДата();
        НоваяЗапись.Документ = Ссылка;
        НоваяЗапись.Товар = СтрокаТоп.Номенклатура;
        НоваяЗапись.Было = СтрокаТоп.КоличествоБыло;
        НоваяЗапись.Стало = СтрокаТоп.Количество;
        НоваяЗапись.Остаток = СтрокаТоп.Остаток;
        НоваяЗапись.Резерв = СтрокаТоп.Резерв;
        НоваяЗапись.Пользователь = ПараметрыСеанса.ТекущийПользователь;
        
    КонецЕсли;
    
КонецЦикла;

// Записываем всё одним махом
Если НаборЗаписей.Количество() > 0 Тогда
    // Ложь - означает, что мы не заменяем существующие записи по отбору, а добавляем
    // В данном случае зависит от логики регистра (периодический или нет)
    НаборЗаписей.Записать(Ложь); 
КонецЕсли;

Рекомендации по тексту запроса

В предоставленном коде запрос содержит вложенные запросы и группировки по ресурсам, что может замедлять работу. Разберем, как улучшить текст запроса для получения остатков. Вместо использования секции ГДЕ для фильтрации по номенклатуре в уже полученной виртуальной таблице, следует передавать параметры непосредственно в саму таблицу.

Сравним подходы:

Плохо: РегистрНакопления.ТоварыНаСкладах.Остатки() КАК Остатки ГДЕ Остатки.Номенклатура = &Номенклатура

Хорошо: РегистрНакопления.ТоварыНаСкладах.Остатки(, Номенклатура В (ВЫБРАТЬ Номенклатура ИЗ СписокНоменклатурыДокумента))

Использование временных таблиц через МенеджерВременныхТаблиц позволит системе 1С построить более эффективный план запроса. Сначала поместите в ВТ_ТоварыДокумента список всей номенклатуры из текущей ссылки, а затем соедините эту временную таблицу с виртуальными таблицами остатков и резервов.

Итоги

Для успешного решения задачи выбора и записи значений необходимо соблюдать три правила:

  1. Не оставлять пустых циклов по выборке запроса, если планируете использовать данные из нее позже.
  2. Использовать поиск в Таблице Значений по индексированным колонкам для сопоставления данных запроса и строк документа.
  3. Работать с наборами записей целиком, избегая метода Записать() внутри циклов.

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

← На главную