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

· 10 мин

Содержание

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

Механизм параметров — часть платформы 8.3, а не конфигурации. Синтаксис, коллекции в условии В, составные типы и необязательные отборы работают одинаково в БП, ЗУП, УТ, ERP и в собственной разработке.

Как объявить параметр в тексте запроса

Параметр — это имя с префиксом &. Знак @ в языке запросов 1С не используется, хотя его иногда путают по аналогии с SQL.

ВЫБРАТЬ
    Реализация.Ссылка,
    Реализация.СуммаДокумента
ИЗ
    Документ.РеализацияТоваровУслуг КАК Реализация
ГДЕ
    Реализация.Контрагент = &Контрагент
    И Реализация.Дата МЕЖДУ &НачалоПериода И &КонецПериода

Правила для имени:

  • 🔤 начинается с буквы или подчёркивания, дальше буквы, цифры, _; пробелы недопустимы;
  • 🔁 регистр не важен: &Контрагент и &КОНТРАГЕНТ — один параметр;
  • ♻️ один параметр можно использовать в тексте сколько угодно раз, значение задаётся один раз;
  • 📍 параметр допустим почти везде, где ждут выражение: в ГДЕ, в полях ВЫБРАТЬ, в ВЫБОР КОГДА, в условиях соединения и в параметрах виртуальных таблиц.

Последний пункт часто недооценивают. Отбор внутри параметров виртуальной таблицы работает быстрее, чем тот же отбор в ГДЕ, потому что платформа применяет его до соединения с итогами:

ВЫБРАТЬ
    Остатки.Номенклатура,
    Остатки.КоличествоОстаток
ИЗ
    РегистрНакопления.ТоварыНаСкладах.Остатки(&МоментОстатков, Склад = &Склад) КАК Остатки

Для предопределённых элементов, значений перечислений и пустых ссылок параметр не нужен. Пишите прямо в тексте: ЗНАЧЕНИЕ(Перечисление.ВидыОперацийРеализацияТоваров.ПродажаКомиссия) или ЗНАЧЕНИЕ(Справочник.Контрагенты.ПустаяСсылка). Так запрос короче, и забыть установить значение невозможно.

Для литеральных дат тоже есть функция: ДАТАВРЕМЯ(2025, 1, 1). Но если дата приходит из формы или из кода, всегда передавайте её параметром.

Создание запроса и установка значений параметров

Минимальная рабочая связка — объект Запрос, текст и вызов УстановитьПараметр для каждого имени:

Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
|    Реализация.Ссылка,
|    Реализация.СуммаДокумента
|ИЗ
|    Документ.РеализацияТоваровУслуг КАК Реализация
|ГДЕ
|    Реализация.Контрагент = &Контрагент
|    И Реализация.Дата МЕЖДУ &НачалоПериода И &КонецПериода";

Запрос.УстановитьПараметр("Контрагент", Объект.Контрагент);
Запрос.УстановитьПараметр("НачалоПериода", НачалоДня(ДатаС));
Запрос.УстановитьПараметр("КонецПериода", КонецДня(ДатаПо));

Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
    // обработка строки
КонецЦикла;

Что здесь происходит:

  1. УстановитьПараметр кладёт значение в коллекцию Запрос.Параметры. Это структура, её можно заполнять и напрямую: Запрос.Параметры.Вставить("Контрагент", Ссылка).
  2. Имя передаётся строкой без амперсанда.
  3. Повторный вызов с тем же именем просто перезаписывает значение. Удобно, когда один запрос выполняется в цикле для разных контрагентов: текст задали один раз, в цикле меняете только параметр.

Не забывайте про КонецДня для конца периода. Дата из поля ввода без времени — это 00:00:00. Условие Дата <= &КонецПериода с такой датой отрежет все документы последнего дня, кроме проведённых ровно в полночь.

Как проверить, какие параметры есть в тексте

Метод НайтиПараметры() возвращает описания всех параметров из текста запроса. Пригодится, если текст собирается динамически или приходит извне:

ОписаниеПараметров = Запрос.НайтиПараметры();
Для Каждого Описание Из ОписаниеПараметров Цикл
    Сообщить(Описание.Имя + ": " + Строка(Описание.ТипЗначения));
КонецЦикла;

По ТипЗначения видно, какой тип платформа ожидает в этом месте. Так удобно ловить опечатки в именах до выполнения.

Пакетный запрос и временные таблицы

В пакетном запросе (части разделены ;) параметры общие для всего пакета. &Организация, установленная один раз, видна во всех подзапросах.

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

Массив, список значений и оператор В

Чтобы отобрать записи по нескольким значениям, передайте коллекцию и используйте В:

Запрос.Текст =
"ВЫБРАТЬ
|    Товары.Ссылка,
|    Товары.Наименование
|ИЗ
|    Справочник.Номенклатура КАК Товары
|ГДЕ
|    Товары.Ссылка В (&СписокТоваров)";

МассивТоваров = Новый Массив;
МассивТоваров.Добавить(Товар1);
МассивТоваров.Добавить(Товар2);
Запрос.УстановитьПараметр("СписокТоваров", МассивТоваров);

Что можно передать в В (&Параметр):

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

Список из табличной части выгружается одной строкой: Объект.Товары.ВыгрузитьКолонку("Номенклатура"). Это готовый массив для параметра.

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

Массив из нескольких тысяч элементов превращается в длинный список значений внутри SQL-запроса. На больших объёмах это заметно медленнее, чем соединение с временной таблицей. Ориентир из практики: от нескольких сотен значений — уже повод перейти на временную таблицу.

Таблица значений как источник данных

Таблицу значений можно передать параметром и выбрать из неё во временную таблицу:

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

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

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

Выбирать из параметра-таблицы можно только с помещением во временную таблицу (ПОМЕСТИТЬ). Дальше с ней работают как с обычным источником: соединяют, группируют, фильтруют.

Составные типы, ссылки и параметры

Реквизит составного типа (например, Регистратор регистра или Субконто1 на счёте) можно сравнивать с параметром любого из входящих типов. Запрос Движения.Регистратор = &Документ работает, если в &Документ лежит ссылка на один из документов-регистраторов.

Сложнее, когда нужно отобрать записи по типу или обратиться к реквизиту конкретного вида документа:

  • 🧩 ССЫЛКА проверяет тип ссылки: Движения.Регистратор ССЫЛКА Документ.ПоступлениеТоваровУслуг;
  • 🎯 ВЫРАЗИТЬ приводит поле к одному типу и даёт доступ к его реквизитам: ВЫРАЗИТЬ(Движения.Регистратор КАК Документ.ПоступлениеТоваровУслуг).Контрагент = &Контрагент;
  • 🏷 ТИП() и ТИПЗНАЧЕНИЯ() сравнивают типы в выражениях: ТИПЗНАЧЕНИЯ(Движения.Регистратор) = ТИП(Документ.ПоступлениеТоваровУслуг).

Без ВЫРАЗИТЬ обращение Регистратор.Контрагент к составному полю заставляет платформу соединить запрос со всеми таблицами типов, где такой реквизит есть. На регистре с двадцатью регистраторами это двадцать соединений. Приведение типа сокращает их до одного.

В параметр можно передать и тип, и массив ссылок разных типов. Условие Движения.Регистратор В (&Документы) работает, даже если в массиве лежат и реализации, и поступления.

Неопределено, NULL и пустая ссылка

Это три разных значения, и путать их в параметрах — частый источник пустых выборок:

ЗначениеГде встречаетсяКак проверить в запросе
НеопределеноНезаполненный реквизит составного типаПоле = НЕОПРЕДЕЛЕНО
NULLНет пары при левом соединенииПоле ЕСТЬ NULL
Пустая ссылкаНезаполненный реквизит ссылочного типаПоле = ЗНАЧЕНИЕ(Справочник.X.ПустаяСсылка)

Сравнение Поле = &Параметр с NULL никогда не даёт истину. Если в параметре оказался NULL, условие отсеет все строки.

Необязательные отборы: пустой параметр и сборка условий

Типичная задача: в форме отчёта есть поля «Контрагент» и «Склад», и если поле пустое, отбирать по нему не нужно. Есть три способа.

Способ 1. Условие с проверкой параметра

ГДЕ
    (&Склад = ЗНАЧЕНИЕ(Справочник.Склады.ПустаяСсылка)
        ИЛИ Остатки.Склад = &Склад)

Плюс — текст запроса не меняется. Минус — ИЛИ мешает СУБД использовать индекс по полю, на больших таблицах запрос замедляется. Подходит для справочников и небольших выборок.

Вариант с флагом читается понятнее: передайте отдельный параметр &ОтборПоСкладу (Булево) и пишите (НЕ &ОтборПоСкладу ИЛИ Остатки.Склад = &Склад).

Способ 2. Сборка текста в коде

Условия добавляются в текст только когда отбор задан:

ТекстЗапроса =
"ВЫБРАТЬ
|    Остатки.Номенклатура,
|    Остатки.КоличествоОстаток
|ИЗ
|    РегистрНакопления.ТоварыНаСкладах.Остатки(&Период, &УсловиеОстатков) КАК Остатки";

Условия = Новый Массив;
Если ЗначениеЗаполнено(Склад) Тогда
    Условия.Добавить("Склад = &Склад");
    Запрос.УстановитьПараметр("Склад", Склад);
КонецЕсли;
Если ЗначениеЗаполнено(Номенклатура) Тогда
    Условия.Добавить("Номенклатура = &Номенклатура");
    Запрос.УстановитьПараметр("Номенклатура", Номенклатура);
КонецЕсли;

Если Условия.Количество() = 0 Тогда
    ТекстУсловия = "ИСТИНА";
Иначе
    ТекстУсловия = СтрСоединить(Условия, " И ");
КонецЕсли;

Запрос.Текст = СтрЗаменить(ТекстЗапроса, "&УсловиеОстатков", ТекстУсловия);
Запрос.УстановитьПараметр("Период", КонецДня(ДатаОтчета));

Здесь &УсловиеОстатков — метка для замены, а не настоящий параметр: её заменяют до выполнения. В итоговый текст попадают только реальные условия, и СУБД строит оптимальный план. Значения пользователя по-прежнему идут через УстановитьПараметр, а не склейкой строк — подставлять в текст можно только своё служебное условие.

СтрСоединить доступна с платформы 8.3.6. Параметры, которых нет в итоговом тексте, устанавливать не нужно, но и лишний параметр ошибки не вызовет.

Способ 3. Система компоновки данных

В СКД отборы пользователя накладываются автоматически, а параметры, найденные в тексте запроса, сами появляются на вкладке «Параметры» схемы. Если отбор не включён, СКД не добавляет условие в итоговый запрос. Для отчётов это самый удобный путь; для кода обработок и проведения обычно хватает первых двух.

СпособКогда выбиратьРиск
&Параметр = Пусто ИЛИ Поле = &ПараметрМаленькие таблицы, быстро написатьМедленно на больших регистрах
Сборка текстаКод обработок, проведение, обменыОшибка в склейке ломает синтаксис
СКДОтчёты с пользовательскими отборамиНе для кода проведения

Производительность и ошибки при работе с параметрами

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

Ещё несколько вещей, которые влияют на скорость:

  • 🚫 Склейка значений со строкой. "ГДЕ Код = """ + Код + """" вместо параметра — это и уязвимость (пользователь может подставить кусок запроса), и проблемы с форматом дат и чисел. Платформа сама приводит параметр к нужному типу, при склейке это ваша забота.
  • 🐢 Запрос в цикле. Выполнять один и тот же запрос с разными параметрами для каждой строки табличной части — медленно. Передайте массив всех значений и получите результат одним запросом, затем разберите его по ключу.
  • 🔍 Отбор в ГДЕ вместо параметров виртуальной таблицы. Для Остатки, Обороты, СрезПоследних условие во втором параметре таблицы отрабатывает раньше и легче.
  • 🧮 Разыменование составных полей без ВЫРАЗИТЬ — лишние соединения, описанные выше.

Момент времени и граница в параметре периода

Для остатков на момент документа передают не дату, а момент или границу:

Запрос.УстановитьПараметр("МоментОстатков",
    Новый Граница(Документ.МоментВремени(), ВидГраницы.Исключая));

ВидГраницы.Исключая даёт остатки до движений самого документа, Включая — с ними. Простая дата не различает документы внутри одной секунды: все движения с этим временем учитываются одинаково, независимо от того, какой документ стоит раньше в последовательности. Отсюда «плавающие» остатки при перепроведении документов с одинаковым временем.

Частые вопросы

Можно ли передать в один параметр значения разных типов?
Да. Массив может содержать ссылки разных справочников или документов, и условие В (&Параметр) отработает по всем. Для одиночного значения тип определяется тем, что вы передали.
Чем `Запрос.Параметры.Вставить` отличается от `УстановитьПараметр`?
По результату ничем: оба кладут значение в структуру параметров. УстановитьПараметр короче и привычнее, Параметры удобна, когда нужно скопировать набор параметров из одного запроса в другой.
Можно ли передать параметром имя таблицы или поля?
Нет. Параметр подставляет только значение. Имена таблиц, полей и порядок сортировки меняют сборкой текста запроса, как в способе 2.
Работают ли параметры в консоли запросов?
Да. Консоль находит параметры в тексте и показывает их списком, значения заполняются вручную, включая массивы и списки значений. Это быстрый способ отладить запрос до переноса в код.
Нужно ли устанавливать параметр, если он используется только в одной части пакета?
Да, если эта часть есть в тексте. Параметры общие для пакета, и платформа требует значение для каждого найденного имени.