Параметры запроса

Учимся передавать значения в запрос снаружи через параметры — и понимаем, почему склеивать значения прямо в текст запроса нельзя.

Параметр запроса — это именованное значение, которое передаётся в запрос из кода отдельно от его текста; в самом тексте на него ссылаются через амперсанд: &ИмяПараметра.

Запрос почти никогда не бывает полностью статичным. «Товары дороже какой-то суммы», «документы за такой-то период», «остатки по этому складу» — конкретное значение известно только во время работы программы. Параметры — это штатный и единственно правильный способ передать такое значение внутрь запроса.

Почему это встречается буквально в каждой обработке и отчёте? Потому что данные всегда зависят от выбора пользователя или от текущего контекста. Указали в форме период и контрагента — эти значения нужно донести до запроса. Документ проводится и считает остатки на свою дату — дата документа уходит в запрос параметром. Запросов без параметров в реальном коде почти не бывает: чистая статика — это разве что справочные списки. Поэтому освоить параметры так же важно, как саму конструкцию ВЫБРАТЬ.

Как это выглядит

В тексте запроса вместо конкретного значения ставят имя параметра с амперсандом, а перед выполнением задают его значение методом УстановитьПараметр():

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

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

Обратите внимание на две вещи. В тексте — &МинЦена с амперсандом. А в вызове УстановитьПараметр("МинЦена", 1000) имя передаётся строкой без амперсанда. Это частый источник путаницы: амперсанд ставится только в тексте запроса, а в коде имя параметра — обычная строка.

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

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

Запрос.УстановитьПараметр("Склад", ВыбранныйСклад);

Здесь ВыбранныйСклад — ссылка на элемент справочника «Склады», которую программа получила, например, из формы. Мы передаём саму ссылку, а не её код или наименование.

Зачем параметры, а не подстановка строк

Технически ничто не мешает собрать текст запроса конкатенацией — «просто вставить значение прямо в строку». Но это грубая ошибка, и вот почему.

1. Защита от подмены запроса (SQL-инъекция)

Если склеивать в текст пользовательский ввод, кто-то может подсунуть туда фрагмент, который исказит смысл запроса. Сравните. Опасный код:

// ТАК ДЕЛАТЬ НЕЛЬЗЯ
Запрос.Текст = "ВЫБРАТЬ * ИЗ Справочник.Контрагенты
    |ГДЕ Наименование = """" + ВводПользователя + """"";

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

2. Корректные типы и локаль

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

3. Кэш и производительность

СУБД кэширует план запроса по его тексту. Если текст каждый раз новый (значение вклеено внутрь), кэш не срабатывает, и план строится заново. Когда же значение вынесено в параметр, текст стабилен — один и тот же запрос с разными параметрами переиспользует готовый план. На высоконагруженной базе это ощутимо.

Параметры в условии ГДЕ

Чаще всего параметры живут именно в ГДЕ. Их можно комбинировать и использовать в любых операторах сравнения:

ГДЕ
    Заказ.Дата МЕЖДУ &НачалоПериода И &КонецПериода
    И Заказ.Контрагент = &Контрагент
    И Заказ.Проведён = ИСТИНА

Задаём их так:

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

Здесь НачалоДня() и КонецДня() — обычные функции встроенного языка: они приводят дату к 00:00:00 и 23:59:59, чтобы в отбор попал весь день целиком. Это типичный приём: подготовку значений делают в коде, а в запрос отдают уже готовые параметры.

Отдельно полезен оператор В (аналог SQL IN) с параметром-массивом — отобрать строки по списку значений:

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

Как это работает

Когда вы вызываете УстановитьПараметр("Имя", Значение), платформа кладёт значение во внутренний словарь запроса. При Выполнить() она отправляет СУБД параметризованный запрос: текст с плейсхолдерами и отдельно — набор значений. СУБД получает эти два потока раздельно, поэтому значение физически не может «стать кодом». Это тот же механизм, что prepared statements в мире SQL, — и именно он даёт и безопасность, и переиспользование плана.

Если параметр объявлен в тексте (&МинЦена), но забыт в коде — при выполнении вы получите ошибку о незаданном параметре. И наоборот, лишний УстановитьПараметр(), которого нет в тексте, платформа просто проигнорирует.

Есть и приятный бонус для отладки. Один и тот же объект Запрос можно выполнить несколько раз, каждый раз меняя лишь значения параметров через УстановитьПараметр(), — текст переписывать не нужно. Это удобно, когда один и тот же отбор надо прогнать по разным датам или контрагентам в цикле: текст запроса задаётся один раз до цикла, а внутри меняется только параметр. И для СУБД это идеальный сценарий — план запроса строится однажды, а дальше переиспользуется, потому что текст неизменен.

Частые ошибки

Ставят амперсанд не там. Правило простое: амперсанд & — только в тексте запроса. В УстановитьПараметр("МинЦена", ...) имя без амперсанда. Написать УстановитьПараметр("&МинЦена", ...) — ошибка.

Склеивают значение в строку «по-быстрому». Даже если ввод «свой» и кажется безопасным, конкатенация ломает кэш плана и рано или поздно спотыкается о кавычки, даты или спецсимволы. Параметр — всегда.

Передают код или наименование вместо ссылки. Для отбора по контрагенту в параметр кладут ссылку на элемент справочника, а не строку с его названием. Строка не совпадёт со ссылочным полем, и запрос вернёт пусто.

Забывают задать параметр. Объявили &Склад в тексте, но не вызвали УстановитьПараметр("Склад", ...) — получите исключение при Выполнить(). Держите текст и установку параметров рядом.

Путают имя параметра с полем. &Контрагент — это внешнее значение, а Заказ.Контрагент — поле таблицы. В условии Заказ.Контрагент = &Контрагент слева поле, справа параметр — так и должно быть.

Итоги

  • Параметр — именованное значение, переданное снаружи; в тексте запроса — через &Имя, в коде — через УстановитьПараметр("Имя", Значение) (в коде имя без амперсанда).
  • Значением может быть число, дата, строка, булево, ссылка, массив, список — платформа сама корректно передаёт тип в СУБД.
  • Параметры защищают от подмены запроса, снимают заботу о форматировании дат и чисел и позволяют СУБД переиспользовать план запроса.
  • Чаще всего параметры стоят в ГДЕ; с оператором В (&Список) удобно отбирать по массиву значений.
  • Никогда не склеивайте значения прямо в текст запроса — используйте параметры даже для «безопасного» ввода.
Проверьте себя
1. В тексте запроса параметр записан как &МинСумма. Как правильно задать его значение в коде?
AЗапрос.УстановитьПараметр("&МинСумма", 1000)
BЗапрос.УстановитьПараметр("МинСумма", 1000)
CЗапрос.МинСумма = 1000
DЗапрос.Текст = Запрос.Текст + " 1000"
2. Почему нельзя собирать текст запроса конкатенацией, вставляя значение прямо в строку?
AКонкатенация строк в 1С запрещена синтаксически
BТак запрос будет выполняться только на клиенте
CЭто открывает путь к подмене запроса, ломает форматы типов и мешает СУБД переиспользовать план
DВставленное значение всегда обрезается до 10 символов