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