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