Передаём таблицу значений в запрос и между процедурами 1С

· 9 мин

Содержание

Таблицу значений передают в запрос через Запрос.УстановитьПараметр("Таблица", ТЗ), а в тексте запроса выбирают из неё с помещением во временную таблицу: ВЫБРАТЬ ... ПОМЕСТИТЬ ВТ ИЗ &Таблица КАК Т. Дальше временную таблицу соединяют с таблицами базы. Главное условие — у каждой колонки должен быть задан тип.

Если таблица нужна не запросу, а другой процедуре, всё проще: объект передаётся по ссылке. Сложности начинаются на границе клиента и сервера: на тонком клиенте типа ТаблицаЗначений нет вовсе, и передавать приходится реквизит формы, массив или адрес во временном хранилище. Ещё один источник проблем — скорость: таблица на десятки тысяч строк при каждом выполнении запроса заново переносится в СУБД.

Как передать таблицу значений в запрос: базовый синтаксис

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

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

Запрос.УстановитьПараметр("ТаблицаТоваров", ТаблицаТоваров);
Результат = Запрос.Выполнить().Выгрузить();

Имя регистра в примере условное — подставьте свой. Что здесь существенно:

  • 📌 Запрос к &ТаблицаТоваров — отдельный, с ПОМЕСТИТЬ. Соединять параметр-таблицу с таблицами базы в одном запросе платформа не даёт.
  • 📌 Имена полей в Т.Номенклатура совпадают с именами колонок таблицы значений. Регистр букв не важен, написание — важно.
  • 📌 Из таблицы можно выбрать не все колонки. Лишние колонки всё равно уедут на сервер СУБД, поэтому их лучше отрезать заранее (об этом ниже).
  • 📌 Отбор по временной таблице внутри параметров виртуальной таблицы Остатки(...) — правильный приём: СУБД считает остатки только по нужным позициям.

Табличную часть документа передают так же, но надёжнее сначала выгрузить её: Объект.Товары.Выгрузить(). Результат уже типизирован по реквизитам табличной части.

Типизация колонок и другие ограничения табличного параметра

Почти все проблемы с табличным параметром растут из колонок без типа. Если создать таблицу так:

ТЗ = Новый ТаблицаЗначений;
ТЗ.Колонки.Добавить("Номенклатура");
ТЗ.Колонки.Добавить("Количество");

то запрос упадёт: платформа не знает, какое поле создать во временной таблице. Правильный вариант — с описанием типов:

ТЗ = Новый ТаблицаЗначений;
ТЗ.Колонки.Добавить("Номенклатура",
	Новый ОписаниеТипов("СправочникСсылка.Номенклатура"));
ТЗ.Колонки.Добавить("Количество",
	Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(15, 3)));
ТЗ.Колонки.Добавить("Комментарий",
	Новый ОписаниеТипов("Строка", , Новый КвалификаторыСтроки(150)));
ТЗ.Колонки.Добавить("ДатаОтгрузки",
	Новый ОписаниеТипов("Дата", , , Новый КвалификаторыДаты(ЧастиДаты.Дата)));

Что ещё ограничивает табличный параметр:

ОграничениеПочему мешаетЧто делать
Колонка без типаНельзя построить поле временной таблицыЗадать ОписаниеТипов
Строка неограниченной длиныТакие поля нельзя сравнивать, группировать, использовать в соединенииУказать длину в КвалификаторыСтроки
Колонка с вложенной таблицей, структурой, массивомЭти типы не хранятся в базе и в запрос не попадутРазвернуть в плоские строки или убрать колонку
Составной тип из многих ссылокПоле в СУБД становится составным, соединения тяжелеютСузить тип до реально нужного
ДанныеФормыКоллекция вместо таблицыВ запрос передаётся только «настоящая» таблица значенийПреобразовать на сервере в ТаблицаЗначений

Таблица, полученная из результата другого запроса (Результат.Выгрузить()), уже типизирована. Её можно передавать дальше без доработок — но лучше вообще не выгружать, а держать данные во временной таблице (см. раздел про менеджер временных таблиц).

Типичный сценарий — табличная часть формы, которую нужно проверить запросом. На сервере сначала получают объект, затем выгружают табличную часть:

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

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

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

Ошибки при передаче таблицы в параметр запроса и их диагностика

Ошибки здесь возникают в момент Запрос.Выполнить(), а не при установке параметра. Поэтому отладку начинайте с проверки самой таблицы: в отладчике откройте значение параметра и посмотрите колонки и их типы.

Диагностика: выберите симптом

Ещё одна ловушка — имя параметра. УстановитьПараметр("ТаблицаТоваров", ...) и &Таблицатоваров в тексте — одно и то же, а &ТаблицаТовара — уже другой параметр, и запрос сообщит, что параметр не задан.

Большие таблицы в параметре: как ускорить запрос

Таблица значений из параметра не живёт в базе. Платформа при выполнении запроса создаёт временную таблицу в СУБД и построчно переносит туда данные. На сотнях строк это незаметно, на десятках тысяч — становится узким местом.

Что реально помогает:

  1. Отрезать лишние колонки. ТЗ.Скопировать(, "Номенклатура, Количество") вернёт копию только с нужными колонками. Меньше данных — быстрее перенос.
  2. Свернуть дубли. ТЗ.Свернуть("Номенклатура", "Количество") склеит повторяющиеся позиции и сложит количество. Если по одному товару было 20 строк, во временную таблицу уйдёт одна.
  3. Проиндексировать временную таблицу. Если по ВТ дальше идёт соединение, добавьте индекс по полю соединения:
|ВЫБРАТЬ
|	Т.Номенклатура КАК Номенклатура,
|	Т.Количество КАК Количество
|ПОМЕСТИТЬ ВТ_Товары
|ИЗ
|	&ТаблицаТоваров КАК Т
|
|ИНДЕКСИРОВАТЬ ПО
|	Номенклатура
  1. Не гонять данные туда-обратно. Самая дорогая схема: выгрузить результат запроса в таблицу, обработать в цикле, снова передать в запрос. Если обработка выражается в терминах запроса, оставьте данные во временной таблице.
  2. Сузить типы. Колонка с типом «любая ссылка» превращается в составное поле. Одна конкретная ссылка — простое поле и быстрое соединение.

Индекс ускоряет соединение, но не бесплатен: его построение тоже занимает время. На таблице в 50–100 строк индекс не нужен.

Для поиска внутри самой таблицы значений (без запроса) работает свой механизм — индексы коллекции: ТЗ.Индексы.Добавить("Номенклатура"). После этого ТЗ.НайтиСтроки(Новый Структура("Номенклатура", Ссылка)) ищет без полного перебора. К запросу этот индекс отношения не имеет.

Таблица значений как параметр процедуры и серверного вызова

Внутри одного контекста

В процедуру таблица передаётся по ссылке. Изменения строк внутри процедуры видны вызывающему коду:

Процедура ОбнулитьКоличество(Таблица)
	Для Каждого Строка Из Таблица Цикл
		Строка.Количество = 0;
	КонецЦикла;
КонецПроцедуры

Ключевое слово Знач здесь не спасает: копируется ссылка, а не содержимое. Строки по-прежнему общие. Нужна независимая копия — делайте Таблица.Скопировать() явно.

Между клиентом и сервером

На тонком клиенте и в веб-клиенте типа ТаблицаЗначений нет. В форме таблица хранится как реквизит, а на клиенте видна как ДанныеФормыКоллекция. Поэтому из клиентской процедуры передать «таблицу значений» на сервер нельзя — передаётся либо реквизит формы, либо данные, которые сериализуются.

Рабочие варианты:

  • 🔄 Реквизит формы. Таблицу кладут в реквизит типа ТаблицаЗначений, а в серверной процедуре получают объект: ТЗ = РеквизитФормыВЗначение("ТаблицаДанных"). После обработки возвращают: ЗначениеВРеквизитФормы(ТЗ, "ТаблицаДанных").
  • 🔄 Массив структур. На клиенте собирают Массив из Структура с примитивными значениями и ссылками, на сервере превращают в таблицу.
  • 🔄 Временное хранилище. Сервер кладёт таблицу через ПоместитьВоВременноеХранилище(ТЗ, УникальныйИдентификатор) и возвращает клиенту только адрес-строку. Следующий серверный вызов достаёт таблицу через ПолучитьИзВременногоХранилища(Адрес). Клиент данные не гоняет вовсе.

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

&НаСервереБезКонтекста
Функция РассчитатьИтог(Знач МассивСтрок)
	// чтение без изменения: обратно на клиент данные не вернутся
КонецФункции

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

Продвинутые приёмы: менеджер временных таблиц, СКД, фоновые задания

Менеджер временных таблиц

Если таблица нужна в нескольких запросах подряд, не передавайте её каждый раз. Один раз поместите во временную таблицу через общий МенеджерВременныхТаблиц:

МВТ = Новый МенеджерВременныхТаблиц;

Запрос = Новый Запрос;
Запрос.МенеджерВременныхТаблиц = МВТ;
Запрос.Текст = "ВЫБРАТЬ Т.Номенклатура КАК Номенклатура
|ПОМЕСТИТЬ ВТ_Товары
|ИЗ &ТаблицаТоваров КАК Т";
Запрос.УстановитьПараметр("ТаблицаТоваров", ТаблицаТоваров);
Запрос.Выполнить();

// второй запрос видит ВТ_Товары без повторной передачи
Запрос2 = Новый Запрос;
Запрос2.МенеджерВременныхТаблиц = МВТ;
Запрос2.Текст = "ВЫБРАТЬ ВТ.Номенклатура ИЗ ВТ_Товары КАК ВТ";

Временные таблицы живут, пока жив менеджер. По завершении работы вызывайте МВТ.Закрыть(), чтобы освободить ресурсы СУБД сразу, а не при сборке мусора.

Таблица в системе компоновки данных

В СКД таблицу значений подают как внешний набор данных. В схеме создают набор типа «Объект» с именем, например, ДанныеТоваров, описывают поля, а в коде передают структуру:

ВнешниеНаборы = Новый Структура("ДанныеТоваров", ТаблицаТоваров);
ПроцессорКомпоновки = Новый ПроцессорКомпоновкиДанных;
ПроцессорКомпоновки.Инициализировать(МакетКомпоновки, ВнешниеНаборы);

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

Фоновые задания

Параметры фонового задания передаются массивом и должны сериализоваться. Для большой таблицы надёжнее передать адрес временного хранилища, а внутри задания получить данные через ПолучитьИзВременногоХранилища. Результат обратно — тем же способом.

Чем заменить таблицу в параметре запроса

Табличный параметр — не единственный способ передать набор значений. Выбор зависит от того, сколько колонок вам нужно.

СпособКогда подходитМинусы
Массив в условии В (&Массив)Нужен только список ссылок для отбораОдна колонка, нет сопутствующих данных
СписокЗначений в условии ВТо же, когда список уже есть в формеТе же ограничения, что у массива
Таблица значений + ПОМЕСТИТЬНесколько колонок: ссылка, количество, ценаНужна типизация, перенос в СУБД
Временная таблица из другого запросаДанные уже есть в базеТребует общего менеджера временных таблиц
Внешний набор данных СКДОтчёт на компоновкеРаботает только внутри СКД

Практическое правило: для отбора по списку — массив, он не требует временной таблицы. Если к каждой ссылке привязано число или дата и это нужно в расчёте — таблица значений.

Массив тоже требует аккуратности. Пустой массив в условии В (&Массив) даёт пустую выборку, а тысячи элементов раздувают текст SQL-запроса. На больших списках та же схема с временной таблицей работает стабильнее.

Частые вопросы по табличным параметрам запроса

Можно ли соединить &Таблица с регистром в одном запросе без ПОМЕСТИТЬ?
Нет. Выборка из табличного параметра оформляется отдельным запросом пакета с помещением во временную таблицу, а соединение — следующим запросом.
Передаётся ли в запрос порядок строк таблицы?
Нет. Временная таблица порядка не хранит. Нужен порядок — добавьте колонку с номером строки и сортируйте по ней в запросе.
Можно ли передать в запрос дерево значений?
Напрямую — нет. Разверните дерево в плоскую таблицу значений с колонкой уровня или родителя и передайте её.
Видна ли временная таблица из параметра другим пользователям?
Нет. Временные таблицы существуют в рамках своего менеджера временных таблиц или одного запроса и другим сеансам недоступны.
Сколько строк можно передать в табличный параметр?
Жёсткого лимита в коде нет, ограничивают память и скорость переноса в СУБД. На десятках тысяч строк уже стоит думать об оптимизации или о том, чтобы вообще не выгружать данные из базы.