Параметры запроса
Учимся передавать значения в запрос снаружи через параметры — и понимаем, почему склеивать значения прямо в текст запроса нельзя.
Параметр запроса — это именованное значение, которое передаётся в запрос из кода отдельно от его текста; в самом тексте на него ссылаются через амперсанд:
&ИмяПараметра.
Запрос почти никогда не бывает полностью статичным. «Товары дороже какой-то суммы», «документы за такой-то период», «остатки по этому складу» — конкретное значение известно только во время работы программы. Параметры — это штатный и единственно правильный способ передать такое значение внутрь запроса.
Почему это встречается буквально в каждой обработке и отчёте? Потому что данные всегда зависят от выбора пользователя или от текущего контекста. Указали в форме период и контрагента — эти значения нужно донести до запроса. Документ проводится и считает остатки на свою дату — дата документа уходит в запрос параметром. Запросов без параметров в реальном коде почти не бывает: чистая статика — это разве что справочные списки. Поэтому освоить параметры так же важно, как саму конструкцию ВЫБРАТЬ.
Как это выглядит
В тексте запроса вместо конкретного значения ставят имя параметра с амперсандом, а перед выполнением задают его значение методом УстановитьПараметр():
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Номенклатура.Наименование,
| Номенклатура.Цена
|ИЗ
| Справочник.Номенклатура КАК Номенклатура
|ГДЕ
| Номенклатура.Цена >= &МинЦена";
Запрос.УстановитьПараметр("МинЦена", 1000);
Результат = Запрос.Выполнить();Обратите внимание на две вещи. В тексте — &МинЦена с амперсандом. А в вызове УстановитьПараметр("МинЦена", 1000) имя передаётся строкой без амперсанда. Это частый источник путаницы: амперсанд ставится только в тексте запроса, а в коде имя параметра — обычная строка.
Значением параметра может быть что угодно: число, строка, дата, булево, ссылка на объект, и даже коллекция (массив, список значений) или таблица значений. Например, отбор по конкретному складу:
Запрос.Текст =
"ВЫБРАТЬ
| Ост.Номенклатура,
| Ост.КоличествоОстаток
|ИЗ
| РегистрНакопления.ТоварыНаСкладах.Остатки(, Склад = &Склад) КАК Ост";
Запрос.УстановитьПараметр("Склад", ВыбранныйСклад);Здесь ВыбранныйСклад — ссылка на элемент справочника «Склады», которую программа получила, например, из формы. Мы передаём саму ссылку, а не её код или наименование.
Зачем параметры, а не подстановка строк
Технически ничто не мешает собрать текст запроса конкатенацией — «просто вставить значение прямо в строку». Но это грубая ошибка, и вот почему.
1. Защита от подмены запроса (SQL-инъекция)
Если склеивать в текст пользовательский ввод, кто-то может подсунуть туда фрагмент, который исказит смысл запроса. Сравните. Опасный код:
// ТАК ДЕЛАТЬ НЕЛЬЗЯ
Запрос.Текст = "ВЫБРАТЬ * ИЗ Справочник.Контрагенты
|ГДЕ Наименование = """" + ВводПользователя + """"";Если ВводПользователя содержит кавычки и служебные слова, структура запроса «поедет». С параметром это в принципе невозможно: платформа передаёт значение отдельным каналом и никогда не трактует его как часть текста запроса.
2. Корректные типы и локаль
Дату в текст запроса пришлось бы форматировать вручную в специальном формате, число — с правильным разделителем, ссылку — вообще нельзя выразить текстом. Параметр принимает настоящее значение 1С нужного типа, и платформа сама корректно передаёт его в СУБД. Никаких забот о форматах.
3. Кэш и производительность
СУБД кэширует план запроса по его тексту. Если текст каждый раз новый (значение вклеено внутрь), кэш не срабатывает, и план строится заново. Когда же значение вынесено в параметр, текст стабилен — один и тот же запрос с разными параметрами переиспользует готовый план. На высоконагруженной базе это ощутимо.
Параметры в условии ГДЕ
Чаще всего параметры живут именно в ГДЕ. Их можно комбинировать и использовать в любых операторах сравнения:
ГДЕ
Заказ.Дата МЕЖДУ &НачалоПериода И &КонецПериода
И Заказ.Контрагент = &Контрагент
И Заказ.Проведён = ИСТИНАЗадаём их так:
Запрос.УстановитьПараметр("НачалоПериода", НачалоДня(ДатаС));
Запрос.УстановитьПараметр("КонецПериода", КонецДня(ДатаПо));
Запрос.УстановитьПараметр("Контрагент", ВыбранныйКонтрагент);Здесь НачалоДня() и КонецДня() — обычные функции встроенного языка: они приводят дату к 00:00:00 и 23:59:59, чтобы в отбор попал весь день целиком. Это типичный приём: подготовку значений делают в коде, а в запрос отдают уже готовые параметры.
Отдельно полезен оператор В (аналог SQL IN) с параметром-массивом — отобрать строки по списку значений:
ГДЕ
Номенклатура.ВидНоменклатуры В (&СписокВидов)СписокВидов = Новый Массив;
СписокВидов.Добавить(Перечисления.ВидыНоменклатуры.Товар);
СписокВидов.Добавить(Перечисления.ВидыНоменклатуры.Услуга);
Запрос.УстановитьПараметр("СписокВидов", СписокВидов);Как это работает
Когда вы вызываете УстановитьПараметр("Имя", Значение), платформа кладёт значение во внутренний словарь запроса. При Выполнить() она отправляет СУБД параметризованный запрос: текст с плейсхолдерами и отдельно — набор значений. СУБД получает эти два потока раздельно, поэтому значение физически не может «стать кодом». Это тот же механизм, что prepared statements в мире SQL, — и именно он даёт и безопасность, и переиспользование плана.
Если параметр объявлен в тексте (&МинЦена), но забыт в коде — при выполнении вы получите ошибку о незаданном параметре. И наоборот, лишний УстановитьПараметр(), которого нет в тексте, платформа просто проигнорирует.
Есть и приятный бонус для отладки. Один и тот же объект Запрос можно выполнить несколько раз, каждый раз меняя лишь значения параметров через УстановитьПараметр(), — текст переписывать не нужно. Это удобно, когда один и тот же отбор надо прогнать по разным датам или контрагентам в цикле: текст запроса задаётся один раз до цикла, а внутри меняется только параметр. И для СУБД это идеальный сценарий — план запроса строится однажды, а дальше переиспользуется, потому что текст неизменен.
Частые ошибки
Ставят амперсанд не там. Правило простое: амперсанд & — только в тексте запроса. В УстановитьПараметр("МинЦена", ...) имя без амперсанда. Написать УстановитьПараметр("&МинЦена", ...) — ошибка.
Склеивают значение в строку «по-быстрому». Даже если ввод «свой» и кажется безопасным, конкатенация ломает кэш плана и рано или поздно спотыкается о кавычки, даты или спецсимволы. Параметр — всегда.
Передают код или наименование вместо ссылки. Для отбора по контрагенту в параметр кладут ссылку на элемент справочника, а не строку с его названием. Строка не совпадёт со ссылочным полем, и запрос вернёт пусто.
Забывают задать параметр. Объявили &Склад в тексте, но не вызвали УстановитьПараметр("Склад", ...) — получите исключение при Выполнить(). Держите текст и установку параметров рядом.
Путают имя параметра с полем. &Контрагент — это внешнее значение, а Заказ.Контрагент — поле таблицы. В условии Заказ.Контрагент = &Контрагент слева поле, справа параметр — так и должно быть.
Итоги
- Параметр — именованное значение, переданное снаружи; в тексте запроса — через
&Имя, в коде — черезУстановитьПараметр("Имя", Значение)(в коде имя без амперсанда). - Значением может быть число, дата, строка, булево, ссылка, массив, список — платформа сама корректно передаёт тип в СУБД.
- Параметры защищают от подмены запроса, снимают заботу о форматировании дат и чисел и позволяют СУБД переиспользовать план запроса.
- Чаще всего параметры стоят в
ГДЕ; с операторомВ (&Список)удобно отбирать по массиву значений. - Никогда не склеивайте значения прямо в текст запроса — используйте параметры даже для «безопасного» ввода.