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