Как правильно передать таблицу товаров из формы-владельца и получить остатки без запросов в цикле?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

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

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

Почему запрос в цикле — это критическая ошибка?

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

  1. Сетевой оверхед (Network Roundtrips): Каждый одиночный запрос в цикле — это отдельный сетевой вызов от сервера приложений «1С» к СУБД (MS SQL, PostgreSQL и др.) и ожидание ответа. Если в документе 100 строк, сервер выполнит 100 сетевых транзакций вместо одной. Время отклика системы вырастет лавинообразно.
  2. Компиляция планов запросов: Система управления базами данных (СУБД) тратит ресурсы на парсинг текста запроса и построение плана его выполнения при каждом обращении. Один сложный пакетный запрос компилируется один раз, а запросы в цикле заставляют СУБД выполнять одну и ту же работу многократно.
  3. Эффективность кэширования: При получении остатков одним пакетом СУБД считывает данные из индекса один раз (выполняя сканирование или поиск по диапазону). Точечные запросы в цикле заставляют СУБД выполнять многократный поиск Index Seek, нагружая дисковую подсистему сервера.
  4. Блокировки данных: Частые мелкие обращения к таблицам итогов регистров накопления могут вызывать микроконфликты блокировок, что снижает общую параллельность работы пользователей в системе.

Таким образом, скорость выполнения пакетного запроса будет в десятки, а иногда и в сотни раз выше, чем у схемы с запросом в цикле. Наша главная задача — собрать все исходные данные, передать их на сервер одним пакетом, выполнить одно обращение к БД и вернуть готовый результат.

Решение 1. Пакетный запрос с передачей таблицы значений во временную таблицу

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

Рассмотрим пример реализации этой логики. Поскольку напрямую передать коллекцию строк формы (например, ДанныеФормыКоллекция) во временную таблицу запроса нельзя, мы должны сначала выгрузить ее в полноценную ТаблицаЗначений. Обратите внимание, что объект ВладелецФормы доступен только в клиентском контексте, поэтому выгрузку мы делаем на клиенте или передаем данные через параметры.

Посмотрим на пример кода серверной функции, которая принимает таблицу товаров, выполняет пакетный запрос и возвращает заполненную таблицу уже с остатками:


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

Важный нюанс оптимизации: Обратите внимание на условие в параметрах виртуальной таблицы остатков: Номенклатура ИЗ (ВЫБРАТЬ РАЗЛИЧНЫЕ Номенклатура ИЗ ВТ_Товары). Это критически важно для производительности. Если этого не сделать, СУБД будет выбирать остатки вообще по всей номенклатуре склада, а уже потом соединять таблицы. Фильтрация на уровне параметров виртуальной таблицы позволяет получить из базы данных только нужные нам строки.

Решение 2. Архитектурный подход через БСП (механизм заполнения объектов)

Выясним причину, почему работа напрямую через свойство ВладелецФормы считается плохим тоном в современной разработке. Дело в том, что внешние обработки в современных конфигурациях регистрируются в справочнике «Дополнительные отчеты и обработки» Библиотеки Стандартных Подсистем (БСП). Для сценариев заполнения документов в БСП предусмотрен специальный тип команды — «ЗаполнениеФормы» (вид обработки — «ЗаполнениеОбъекта»).

При использовании этого механизма нам вообще не нужно вручную прописывать открытие формы обработки и передавать туда владельца. Платформа и БСП делают это автоматически. В модуле объекта нашей внешней обработки достаточно прописать экспортную процедуру ВыполнитьКоманду. В параметрах этой процедуры БСП передает нам контекст вызывающей формы документа прямо на сервере!

Разберем по шагам, как выглядит этот изящный и правильный код в модуле объекта внешней обработки:


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

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

Решение 3. Передача данных через Временное Хранилище

Рассмотрим ситуацию, когда форму внешней обработки все-таки необходимо открыть интерактивно (например, чтобы пользователь мог выбрать конкретный склад для расчета остатков или настроить формулы расчета цен перед заполнением). В этом случае тип команды БСП будет «ОткрытиеФормы».

Здесь разработчики часто наступают на грабли, пытаясь передать таблицу товаров напрямую с клиента в параметры открываемой формы. Платформа незамедлительно выдаст ошибку: «Попытка передачи с клиента на сервер мутабельного значения». Объект типа ТаблицаЗначений не может путешествовать между клиентом и сервером напрямую.

Правильным решением этой архитектурной проблемы является использование Временного Хранилища платформы. Разберем этот алгоритм по шагам:

  1. В форме документа-источника при нажатии кнопки заполнения мы уходим на сервер, выгружаем табличную часть в ТаблицаЗначений и помещаем её во временное хранилище, привязывая адрес к уникальному идентификатору формы.
  2. Возвращаем полученный адрес (строку) на клиент.
  3. Передаем этот адрес в качестве параметра при открытии формы обработки.
  4. В форме обработки при создании на сервере извлекаем таблицу из временного хранилища, выполняем наш пакетный запрос и заполняем данные.

Посмотрим, как выглядит реализация этой логики в коде.

Код в форме документа-источника (откуда вызывается обработка):


&НаКлиенте
Процедура ВыполнитьЗаполнение(Команда)
    
    // Шаг 1: Помещаем данные в хранилище на сервере
    АдресДанных = ПоместитьДанныеВХранилищеНаСервере();
    
    // Шаг 2: Формируем параметры открытия формы обработки
    ПараметрыОткрытия = Новый Структура;
    ПараметрыОткрытия.Вставить("АдресДанных", АдресДанных);
    
    // Шаг 3: Открываем форму обработки
    ОткрытьФорму("ВнешняяОбработка.ЗаполнениеТоваров.Форма.Форма", ПараметрыОткрытия, ЭтаФорма);
    
КонецПроцедуры

&НаСервере
Функция ПоместитьДанныеВХранилищеНаСервере()
    
    // Выгружаем ТЧ документа в Таблицу Значений
    ТаблицаДляПередачи = Объект.Товары.Выгрузить();
    
    // Помещаем в хранилище с привязкой к УИД формы, чтобы избежать утечек памяти
    Возврат ПоместитьВоВременноеХранилище(ТаблицаДляПередачи, УникальныйИдентификатор);
    
КонецФункции

Теперь рассмотрим код в форме самой внешней обработки (приемнике данных):


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

Использование этого подхода гарантирует высокую скорость работы системы. Платформа «1С» не тратит ресурсы на сериализацию тяжелых xml/json-структур таблицы значений при передаче между клиентом и сервером, так как все манипуляции с хранилищем происходят непосредственно в оперативной памяти сервера приложений.

Полезные приемы при работе с формой-владельцем

Если вы все же проектируете нестандартный интерфейс взаимодействия форм на клиенте, обязательно используйте следующие системные возможности:

Мы подробно разобрали технические аспекты заполнения таблиц и получения остатков. Избегайте использования запросов в циклах, применяйте пакетные запросы с временными таблицами и всегда следуйте стандартам разработки БСП для построения надежных и быстродействующих систем на платформе «1С:Предприятие 8».

← На главную