Как избежать задвоения данных при формировании оприходования товаров из двух запросов в 1С?

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

При переносе остатков или выравнивании учета часто возникает необходимость сформировать документ ОприходованиеТоваров на основе данных из двух различных регистров накопителя. Например, количественные остатки берутся из регистра товаров на складах (ОстаткиТоваровКомпании), а стоимостные показатели и себестоимость — из регистра партий (ПартииТоваровКомпании).

Типичная ошибка при решении этой задачи — выполнение двух независимых запросов с последующим сопоставлением результатов во вложенных циклах Для каждого в коде 1С. Проанализируем, почему возникает декартово произведение строк и как правильно объединить данные в один запрос.

Выясним причину задвоения количества

Рассмотрим структуру некорректного алгоритма обработчика:


Запрос1 = Новый Запрос; // Запрос по партиям
ТаблицаПартий = Запрос1.Выполнить().Выгрузить();

Запрос2 = Новый Запрос; // Запрос по складам
ТаблицаСклада = Запрос2.Выполнить().Выгрузить();

Для Каждого СтрокаИзПартииТоваров Из ТаблицаПартий Цикл 
    Для Каждого СтрокаИзСкладовТоваров Из ТаблицаСклада Цикл
        Если СтрокаИзПартииТоваров.Номенклатура = СтрокаИзСкладовТоваров.Номенклатура Тогда
            // Добавление строки в документ и запись
        КонецЕсли;
    КонецЦикла;
КонецЦикла;

Проблема заключается в том, что если по одной номенклатуре в регистре партий присутствует несколько записей (например, разный статус партий или документы поставки), а в регистре товаров — несколько складов или заказов, происходит умножение строк (N × M). Строка в документ будет добавляться столько раз, сколько партий найдено для конкретной позиции номенклатуры, из-за чего итоговое количество в документе возрастает в несколько раз.

Главные правила исправления алгоритма

  1. Соединение данных на стороне СУБД: В место перебора двух таблиц через вложенный цикл следует объединять данные в едином запросе с помощью операторов ЛЕВОЕ СОЕДИНЕНИЕ или ПОЛНОЕ СОЕДИНЕНИЕ.
  2. Использование виртуальных таблиц остатков: Для получения итогов на дату нужно обращаться к виртуальной таблице Остатки, а не ОстаткиИОбороты. Это избавит запрос от лишней выборки оборотов и ускорит его работу.
  3. Очистка запроса от конструкций СКД: Поля в фигурных скобках {...} предназначены для построителя отчетов или Системы компоновки данных. В обычном объекте Запрос они перегружают код и затрудняют отладку.
  4. Запись документа строго после заполнения: Вызов метода Записать() внутри цикла приводит к многократной перезаписи документа в базу данных, что сильно замедляет работу и блокирует таблицы. Записывать документ необходимо один раз после добавления всех строк.

Создаем единый запрос для объединения данных

Разберем по шагам, как сформировать запрос, который вернет точно сопоставленные данные по складу, номенклатуре, количеству и средней цене:


Запрос = Новый Запрос;
Запрос.УстановитьПараметр("Период", Новый Граница(КонецДня(ДатаКонечная), ВидГраницы.Включая));

Запрос.Текст = 
    "ВЫБРАТЬ
    |	ОстаткиТоваров.СкладКомпании КАК СкладКомпании,
    |	ОстаткиТоваров.Номенклатура КАК Номенклатура,
    |	ЕСТЬNULL(ОстаткиТоваров.КоличествоКонечныйОстаток, 0) КАК Количество,
    |	ЕСТЬNULL(ПартииТоваров.СтоимостьКонечныйОстаток, 0) КАК Стоимость,
    |	ВЫБОР
    |		КОГДА ЕСТЬNULL(ПартииТоваров.КоличествоКонечныйОстаток, 0) > 0
    |			ТОГДА ПартииТоваров.СтоимостьКонечныйОстаток / ПартииТоваров.КоличествоКонечныйОстаток
    |		ИНАЧЕ 0
    |	КОНЕЦ КАК Цена
    |ИЗ
    |	РегистрНакопления.ОстаткиТоваровКомпании.Остатки(&Период, ) КАК ОстаткиТоваров
    |		ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ПартииТоваровКомпании.Остатки(&Период, ) КАК ПартииТоваров
    |		ПО ОстаткиТоваров.Номенклатура = ПартииТоваров.Номенклатура
    |			И ОстаткиТоваров.СкладКомпании = ПартииТоваров.ДокументПоставки.СкладКомпании
    |ГДЕ
    |	ОстаткиТоваров.КоличествоКонечныйОстаток > 0";

РезультатЗапроса = Запрос.Выполнить();

Заполнение и запись документа

Посмотрим на пример заполнения табличной части документа ОприходованиеТоваров на основе полученной выборки:


ДокументОприходование = Документы.ОприходованиеТоваров.СоздатьДокумент();
ДокументОприходование.Дата = ДатаКонечная;
ДокументОприходование.ВалютаДокумента = Справочники.Валюты.НайтиПоКоду("643");
ДокументОприходование.КурсДокумента = 1;
ДокументОприходование.КратностьДокумента = 1;
ДокументОприходование.ОтражатьВРегламентированномУчете = Истина;
ДокументОприходование.ОтражатьВУправленческомУчете = Истина;

Выборка = РезультатЗапроса.Выбрать();
Пока Выборка.Следующий() Цикл
    ДокументОприходование.СкладКомпании = Выборка.СкладКомпании;
    
    НоваяСтрока = ДокументОприходование.Товары.Добавить();
    НоваяСтрока.Номенклатура = Выборка.Номенклатура;
    НоваяСтрока.Количество = Выборка.Количество;
    НоваяСтрока.ЕдиницаИзмерения = Выборка.Номенклатура.ЕдиницаХраненияОстатков;
    НоваяСтрока.Коэффициент = НоваяСтрока.ЕдиницаИзмерения.Коэффициент;
    НоваяСтрока.Цена = Выборка.Цена;
    НоваяСтрока.Сумма = НоваяСтрока.Количество * НоваяСтрока.Цена;
    НоваяСтрока.ГТД = Выборка.Номенклатура.НомерГТД;
    НоваяСтрока.СтранаПроисхождения = Выборка.Номенклатура.СтранаПроисхождения;
КонецЦикла;

// Записываем документ один раз в конце обработки
ДокументОприходование.Записать(РежимЗаписиДокумента.Запись);

Совет: Для отладки сложных условий объединения нескольких регистров используйте внешнюю обработку Консоль запросов. Она позволяет оперативно проверить результат работы запроса на реальной базе данных до написания кода в модуле.

← На главную