Передача значений в запрос 1С: от одного отбора до массивов и таблиц
Содержание
Параметр в запросе 1С объявляют в тексте через амперсанд — &Контрагент, &НачалоПериода, — а значение передают перед выполнением методом Запрос.УстановитьПараметр("Контрагент", Ссылка). Так в запрос попадают переменные из кода: ссылки, даты, числа, строки, массивы и даже таблицы значений. Склеивать значения со строкой текста запроса не нужно и вредно.
Механизм параметров — часть платформы 8.3, а не конфигурации. Синтаксис, коллекции в условии В, составные типы и необязательные отборы работают одинаково в БП, ЗУП, УТ, ERP и в собственной разработке.
Как объявить параметр в тексте запроса
Параметр — это имя с префиксом &. Знак @ в языке запросов 1С не используется, хотя его иногда путают по аналогии с SQL.
ВЫБРАТЬ
Реализация.Ссылка,
Реализация.СуммаДокумента
ИЗ
Документ.РеализацияТоваровУслуг КАК Реализация
ГДЕ
Реализация.Контрагент = &Контрагент
И Реализация.Дата МЕЖДУ &НачалоПериода И &КонецПериода
Правила для имени:
- 🔤 начинается с буквы или подчёркивания, дальше буквы, цифры,
_; пробелы недопустимы; - 🔁 регистр не важен:
&Контрагенти&КОНТРАГЕНТ— один параметр; - ♻️ один параметр можно использовать в тексте сколько угодно раз, значение задаётся один раз;
- 📍 параметр допустим почти везде, где ждут выражение: в
ГДЕ, в поляхВЫБРАТЬ, вВЫБОР КОГДА, в условиях соединения и в параметрах виртуальных таблиц.
Последний пункт часто недооценивают. Отбор внутри параметров виртуальной таблицы работает быстрее, чем тот же отбор в ГДЕ, потому что платформа применяет его до соединения с итогами:
ВЫБРАТЬ
Остатки.Номенклатура,
Остатки.КоличествоОстаток
ИЗ
РегистрНакопления.ТоварыНаСкладах.Остатки(&МоментОстатков, Склад = &Склад) КАК Остатки
Для предопределённых элементов, значений перечислений и пустых ссылок параметр не нужен. Пишите прямо в тексте: ЗНАЧЕНИЕ(Перечисление.ВидыОперацийРеализацияТоваров.ПродажаКомиссия) или ЗНАЧЕНИЕ(Справочник.Контрагенты.ПустаяСсылка). Так запрос короче, и забыть установить значение невозможно.
Для литеральных дат тоже есть функция: ДАТАВРЕМЯ(2025, 1, 1). Но если дата приходит из формы или из кода, всегда передавайте её параметром.
Создание запроса и установка значений параметров
Минимальная рабочая связка — объект Запрос, текст и вызов УстановитьПараметр для каждого имени:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Реализация.Ссылка,
| Реализация.СуммаДокумента
|ИЗ
| Документ.РеализацияТоваровУслуг КАК Реализация
|ГДЕ
| Реализация.Контрагент = &Контрагент
| И Реализация.Дата МЕЖДУ &НачалоПериода И &КонецПериода";
Запрос.УстановитьПараметр("Контрагент", Объект.Контрагент);
Запрос.УстановитьПараметр("НачалоПериода", НачалоДня(ДатаС));
Запрос.УстановитьПараметр("КонецПериода", КонецДня(ДатаПо));
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
// обработка строки
КонецЦикла;
Что здесь происходит:
УстановитьПараметркладёт значение в коллекциюЗапрос.Параметры. Это структура, её можно заполнять и напрямую:Запрос.Параметры.Вставить("Контрагент", Ссылка).- Имя передаётся строкой без амперсанда.
- Повторный вызов с тем же именем просто перезаписывает значение. Удобно, когда один запрос выполняется в цикле для разных контрагентов: текст задали один раз, в цикле меняете только параметр.
Не забывайте про КонецДня для конца периода. Дата из поля ввода без времени — это 00:00:00. Условие Дата <= &КонецПериода с такой датой отрежет все документы последнего дня, кроме проведённых ровно в полночь.
Как проверить, какие параметры есть в тексте
Метод НайтиПараметры() возвращает описания всех параметров из текста запроса. Пригодится, если текст собирается динамически или приходит извне:
ОписаниеПараметров = Запрос.НайтиПараметры();
Для Каждого Описание Из ОписаниеПараметров Цикл
Сообщить(Описание.Имя + ": " + Строка(Описание.ТипЗначения));
КонецЦикла;
По ТипЗначения видно, какой тип платформа ожидает в этом месте. Так удобно ловить опечатки в именах до выполнения.
Пакетный запрос и временные таблицы
В пакетном запросе (части разделены ;) параметры общие для всего пакета. &Организация, установленная один раз, видна во всех подзапросах.
Если временные таблицы живут в МенеджерВременныхТаблиц и используются несколькими объектами Запрос, параметры у каждого объекта свои. Новый запрос с тем же менеджером видит временные таблицы, но не видит параметры предыдущего запроса.
Массив, список значений и оператор В
Чтобы отобрать записи по нескольким значениям, передайте коллекцию и используйте В:
Запрос.Текст =
"ВЫБРАТЬ
| Товары.Ссылка,
| Товары.Наименование
|ИЗ
| Справочник.Номенклатура КАК Товары
|ГДЕ
| Товары.Ссылка В (&СписокТоваров)";
МассивТоваров = Новый Массив;
МассивТоваров.Добавить(Товар1);
МассивТоваров.Добавить(Товар2);
Запрос.УстановитьПараметр("СписокТоваров", МассивТоваров);
Что можно передать в В (&Параметр):
| Тип значения | Когда удобно | На что смотреть |
|---|---|---|
Массив | Значения собраны в коде | Самый частый вариант |
ФиксированныйМассив | Значение пришло из параметров сеанса или формы | Работает так же, как массив |
СписокЗначений | Отбор выбран пользователем в форме | Берутся значения, пометки и представления не учитываются |
| Таблица значений через временную таблицу | Тысячи значений или отбор по нескольким колонкам | Нужны типизированные колонки (см. ниже) |
Список из табличной части выгружается одной строкой: Объект.Товары.ВыгрузитьКолонку("Номенклатура"). Это готовый массив для параметра.
Пустой массив запрос не ломает. Условие В с пустой коллекцией просто ложно, и выборка будет пустой. Если по смыслу пустой список означает «без отбора», это надо обработать отдельно — об этом раздел про необязательные отборы.
Массив из нескольких тысяч элементов превращается в длинный список значений внутри SQL-запроса. На больших объёмах это заметно медленнее, чем соединение с временной таблицей. Ориентир из практики: от нескольких сотен значений — уже повод перейти на временную таблицу.
Таблица значений как источник данных
Таблицу значений можно передать параметром и выбрать из неё во временную таблицу:
ТЗ = Новый ТаблицаЗначений;
ТЗ.Колонки.Добавить("Номенклатура", Новый ОписаниеТипов("СправочникСсылка.Номенклатура"));
ТЗ.Колонки.Добавить("Количество", Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(15, 3)));
// ... заполнение строк
Запрос = Новый Запрос;
Запрос.МенеджерВременныхТаблиц = Новый МенеджерВременныхТаблиц;
Запрос.Текст =
"ВЫБРАТЬ
| Т.Номенклатура КАК Номенклатура,
| Т.Количество КАК Количество
|ПОМЕСТИТЬ ВТ_Товары
|ИЗ
| &Товары КАК Т
|;
|ВЫБРАТЬ
| ВТ.Номенклатура,
| ВТ.Количество,
| Остатки.КоличествоОстаток
|ИЗ
| ВТ_Товары КАК ВТ
| ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ТоварыНаСкладах.Остатки(
| , Номенклатура В (ВЫБРАТЬ Номенклатура ИЗ ВТ_Товары)) КАК Остатки
| ПО ВТ.Номенклатура = Остатки.Номенклатура";
Запрос.УстановитьПараметр("Товары", ТЗ);
Главное правило: у каждой колонки должен быть явный тип через ОписаниеТипов. Колонка, добавленная без типа, не разберётся запросом, и выполнение упадёт с ошибкой. Таблица, полученная через Выгрузить() из табличной части или другого запроса, типы уже содержит.
Выбирать из параметра-таблицы можно только с помещением во временную таблицу (ПОМЕСТИТЬ). Дальше с ней работают как с обычным источником: соединяют, группируют, фильтруют.
Составные типы, ссылки и параметры
Реквизит составного типа (например, Регистратор регистра или Субконто1 на счёте) можно сравнивать с параметром любого из входящих типов. Запрос Движения.Регистратор = &Документ работает, если в &Документ лежит ссылка на один из документов-регистраторов.
Сложнее, когда нужно отобрать записи по типу или обратиться к реквизиту конкретного вида документа:
- 🧩
ССЫЛКАпроверяет тип ссылки:Движения.Регистратор ССЫЛКА Документ.ПоступлениеТоваровУслуг; - 🎯
ВЫРАЗИТЬприводит поле к одному типу и даёт доступ к его реквизитам:ВЫРАЗИТЬ(Движения.Регистратор КАК Документ.ПоступлениеТоваровУслуг).Контрагент = &Контрагент; - 🏷
ТИП()иТИПЗНАЧЕНИЯ()сравнивают типы в выражениях:ТИПЗНАЧЕНИЯ(Движения.Регистратор) = ТИП(Документ.ПоступлениеТоваровУслуг).
Без ВЫРАЗИТЬ обращение Регистратор.Контрагент к составному полю заставляет платформу соединить запрос со всеми таблицами типов, где такой реквизит есть. На регистре с двадцатью регистраторами это двадцать соединений. Приведение типа сокращает их до одного.
В параметр можно передать и тип, и массив ссылок разных типов. Условие Движения.Регистратор В (&Документы) работает, даже если в массиве лежат и реализации, и поступления.
Неопределено, NULL и пустая ссылка
Это три разных значения, и путать их в параметрах — частый источник пустых выборок:
| Значение | Где встречается | Как проверить в запросе |
|---|---|---|
Неопределено | Незаполненный реквизит составного типа | Поле = НЕОПРЕДЕЛЕНО |
NULL | Нет пары при левом соединении | Поле ЕСТЬ NULL |
| Пустая ссылка | Незаполненный реквизит ссылочного типа | Поле = ЗНАЧЕНИЕ(Справочник.X.ПустаяСсылка) |
Сравнение Поле = &Параметр с NULL никогда не даёт истину. Если в параметре оказался NULL, условие отсеет все строки.
Необязательные отборы: пустой параметр и сборка условий
Типичная задача: в форме отчёта есть поля «Контрагент» и «Склад», и если поле пустое, отбирать по нему не нужно. Есть три способа.
Способ 1. Условие с проверкой параметра
ГДЕ
(&Склад = ЗНАЧЕНИЕ(Справочник.Склады.ПустаяСсылка)
ИЛИ Остатки.Склад = &Склад)
Плюс — текст запроса не меняется. Минус — ИЛИ мешает СУБД использовать индекс по полю, на больших таблицах запрос замедляется. Подходит для справочников и небольших выборок.
Вариант с флагом читается понятнее: передайте отдельный параметр &ОтборПоСкладу (Булево) и пишите (НЕ &ОтборПоСкладу ИЛИ Остатки.Склад = &Склад).
Способ 2. Сборка текста в коде
Условия добавляются в текст только когда отбор задан:
ТекстЗапроса =
"ВЫБРАТЬ
| Остатки.Номенклатура,
| Остатки.КоличествоОстаток
|ИЗ
| РегистрНакопления.ТоварыНаСкладах.Остатки(&Период, &УсловиеОстатков) КАК Остатки";
Условия = Новый Массив;
Если ЗначениеЗаполнено(Склад) Тогда
Условия.Добавить("Склад = &Склад");
Запрос.УстановитьПараметр("Склад", Склад);
КонецЕсли;
Если ЗначениеЗаполнено(Номенклатура) Тогда
Условия.Добавить("Номенклатура = &Номенклатура");
Запрос.УстановитьПараметр("Номенклатура", Номенклатура);
КонецЕсли;
Если Условия.Количество() = 0 Тогда
ТекстУсловия = "ИСТИНА";
Иначе
ТекстУсловия = СтрСоединить(Условия, " И ");
КонецЕсли;
Запрос.Текст = СтрЗаменить(ТекстЗапроса, "&УсловиеОстатков", ТекстУсловия);
Запрос.УстановитьПараметр("Период", КонецДня(ДатаОтчета));
Здесь &УсловиеОстатков — метка для замены, а не настоящий параметр: её заменяют до выполнения. В итоговый текст попадают только реальные условия, и СУБД строит оптимальный план. Значения пользователя по-прежнему идут через УстановитьПараметр, а не склейкой строк — подставлять в текст можно только своё служебное условие.
СтрСоединить доступна с платформы 8.3.6. Параметры, которых нет в итоговом тексте, устанавливать не нужно, но и лишний параметр ошибки не вызовет.
Способ 3. Система компоновки данных
В СКД отборы пользователя накладываются автоматически, а параметры, найденные в тексте запроса, сами появляются на вкладке «Параметры» схемы. Если отбор не включён, СКД не добавляет условие в итоговый запрос. Для отчётов это самый удобный путь; для кода обработок и проведения обычно хватает первых двух.
| Способ | Когда выбирать | Риск |
|---|---|---|
&Параметр = Пусто ИЛИ Поле = &Параметр | Маленькие таблицы, быстро написать | Медленно на больших регистрах |
| Сборка текста | Код обработок, проведение, обмены | Ошибка в склейке ломает синтаксис |
| СКД | Отчёты с пользовательскими отборами | Не для кода проведения |
Производительность и ошибки при работе с параметрами
Ещё несколько вещей, которые влияют на скорость:
- 🚫 Склейка значений со строкой.
"ГДЕ Код = """ + Код + """"вместо параметра — это и уязвимость (пользователь может подставить кусок запроса), и проблемы с форматом дат и чисел. Платформа сама приводит параметр к нужному типу, при склейке это ваша забота. - 🐢 Запрос в цикле. Выполнять один и тот же запрос с разными параметрами для каждой строки табличной части — медленно. Передайте массив всех значений и получите результат одним запросом, затем разберите его по ключу.
- 🔍 Отбор в
ГДЕвместо параметров виртуальной таблицы. ДляОстатки,Обороты,СрезПоследнихусловие во втором параметре таблицы отрабатывает раньше и легче. - 🧮 Разыменование составных полей без
ВЫРАЗИТЬ— лишние соединения, описанные выше.
Момент времени и граница в параметре периода
Для остатков на момент документа передают не дату, а момент или границу:
Запрос.УстановитьПараметр("МоментОстатков",
Новый Граница(Документ.МоментВремени(), ВидГраницы.Исключая));
ВидГраницы.Исключая даёт остатки до движений самого документа, Включая — с ними. Простая дата не различает документы внутри одной секунды: все движения с этим временем учитываются одинаково, независимо от того, какой документ стоит раньше в последовательности. Отсюда «плавающие» остатки при перепроведении документов с одинаковым временем.
Частые вопросы
Можно ли передать в один параметр значения разных типов?
В (&Параметр) отработает по всем. Для одиночного значения тип определяется тем, что вы передали.Чем `Запрос.Параметры.Вставить` отличается от `УстановитьПараметр`?
УстановитьПараметр короче и привычнее, Параметры удобна, когда нужно скопировать набор параметров из одного запроса в другой.