Как передать табличную часть несохраненного или скопированного документа в запрос в 1С?

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

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

Стандартный и наиболее простой путь — это написать запрос к табличной части документа в базе данных, установив отбор по его ссылке. Однако этот метод моментально перестает работать в двух классических ситуациях:

  1. Документ еще не записан (новый). У него просто нет ссылки в базе данных (значение Документы.[ИмяДокумента].ПустаяСсылка). Соответственно, запрос по такой ссылке вернет пустой результат.
  2. Документ был изменен, но изменения не сохранены. Если пользователь добавил новые строки в табличную часть формы и еще не нажал кнопку «Записать», запрос по ссылке обратится к СУБД и вернет старое состояние табличной части, сохраненное в базе данных. Пользовательские изменения на форме будут проигнорированы.

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

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

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

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

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

Посмотрим на пример кода на сервере:


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

В этом примере мы использовали конструкцию &ТабличнаяЧастьЗаказа КАК ТабличнаяЧастьЗаказа прямо в секции ИЗ нашего запроса. Платформа 1С автоматически преобразует переданный объект ТаблицаЗначений во временную таблицу СУБД, что позволяет нам использовать ее в соединениях и условиях.


Оптимизация 1: Использование внеконтекстных вызовов (&НаСервереБезКонтекста)

Использование директивы &НаСервере (контекстный вызов) влечет за собой передачу всей формы со всеми ее реквизитами, элементами управления и данными с клиента на сервер и обратно. Если документ содержит сотни строк, это создает колоссальную и неоправданную нагрузку на сеть.

Правильным подходом является использование директивы &НаСервереБезКонтекста. Но здесь мы сталкиваемся с особенностью платформы: на клиенте табличная часть представлена типом ДанныеФормыКоллекция, а объект ТаблицаЗначений на тонком клиенте недоступен.

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

Разберем по шагам правильную реализацию этого механизма:


&НаКлиенте
Процедура ПриИзмененииПоля(Элемент)
    // Передаем табличную часть как параметр во внеконтекстный метод
    ПолучитьЦеныНаСервереБезКонтекста(Объект.Товары, Объект.ДополнительнаяЦена);
КонецПроцедуры

&НаСервереБезКонтекста
Процедура ПолучитьЦеныНаСервереБезКонтекста(Знач ТоварыДокумента, Знач ВидЦены)
    
    // На сервере "ДанныеФормыКоллекция" выгружаем в полноценную "ТаблицуЗначений"
    ТаблицаДляЗапроса = ТоварыДокумента.Выгрузить();
    
    Запрос = Новый Запрос;
    // Описание запроса и его выполнение...
    
КонецПроцедуры

Оптимизация 2: Выгрузка только необходимых колонок

Метод Выгрузить() без параметров копирует абсолютно все существующие колонки табличной части (включая номера строк, служебные реквизиты, расчетные суммы, ставки НДС и т.д.). Передавать такую тяжелую и широкую таблицу в СУБД крайне неэффективно.

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

Применим этот подход на практике:


// Выгружаем только две целевые колонки
ТаблицаДляЗапроса = ТоварыДокумента.Выгрузить(, "Номенклатура, Характеристика");

Такой подход значительно сокращает объем передаваемого трафика между сервером приложений 1С и сервером СУБД.


Оптимизация 3: Индексирование временной таблицы

Если в табличной части вашего документа содержится большое количество строк (более нескольких десятков), СУБД при левом соединении временной таблицы с таблицей цен будет выполнять операцию полного сканирования таблицы (Table Scan). Это может привести к серьезным задержкам.

Чтобы этого избежать, временную таблицу, создаваемую на основе переданного параметра, необходимо обязательно проиндексировать по полям связи. В нашем случае это поля Номенклатура и Характеристика.

Рассмотрим подробнее структуру оптимизированного запроса:


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

Важное требование платформы: Жесткая типизация колонок

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

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

Если добавить колонку без явного указания типов (то есть оставить тип колонки неопределенным), СУБД не сможет сопоставить типы данных при создании временной таблицы, и система выдаст ошибку выполнения запроса: «Содержимое объекта данных может быть выбрано только во временную таблицу» или «Неверные параметры».

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


// Создаем таблицу значений вручную
ТЗ = Новый ТаблицаЗначений;

// Обязательно определяем точные типы для каждой колонки!
МассивТиповНоменклатура = Новый Массив;
МассивТиповНоменклатура.Добавить(Тип("СправочникСсылка.Номенклатура"));
ОписаниеТиповНоменклатура = Новый ОписаниеТипов(МассивТиповНоменклатура);

МассивТиповХарактеристика = Новый Массив;
МассивТиповХарактеристика.Добавить(Тип("СправочникСсылка.ХарактеристикиНоменклатуры"));
ОписаниеТиповХарактеристика = Новый ОписаниеТипов(МассивТиповХарактеристика);

// Добавляем типизированные колонки в ТЗ
ТЗ.Колонки.Добавить("Номенклатура", ОписаниеТиповНоменклатура);
ТЗ.Колонки.Добавить("Характеристика", ОписаниеТиповХарактеристика);

// Теперь эту таблицу значений можно безопасно наполнять данными и передавать в запрос

Альтернативный вариант: Передача массива уникальных значений

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

В такой ситуации отличной и более производительной альтернативой является передача простого Массива уникальных ссылок в параметры виртуальной таблицы среза последних.

Разберем этот вариант кода подробнее:


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

Такой подход работает быстрее на небольших и средних объемах данных, поскольку СУБД не тратит системные ресурсы на развертывание структуры временной таблицы в служебной базе данных tempdb.

Подводя итоги, мы рассмотрели различные способы работы с данными табличных частей прямо "на лету" без записи документов. Использование внеконтекстных серверных методов совместно с передачей типизированных таблиц значений или массивов гарантирует высокую производительность системы и обеспечивает корректную работу даже со скопированными и новыми документами.

← На главную