В системе 1С:Предприятие параметры запросов позволяют динамически формировать SQL-подобные конструкции, адаптируя их под конкретные задачи. Одним из неочевидных, но крайне полезных применений является передача технического задания (ТЗ) как параметра. Этот подход актуален при работе с большими объемами данных, когда необходимо гибко фильтровать записи по критериям, заданным в ТЗ, или когда логика отбора слишком сложна для стандартных конструкций языка запросов.

На практике такой механизм востребован в сценариях интеграции, когда внешние системы передают в структурированные условия отбора (например, через JSON или XML), а также при разработке универсальных отчетов, где пользователь может задавать произвольные фильтры. Однако неправильное использование ТЗ как параметра чревато ошибками выполнения, проблемами с производительностью или даже уязвимостями в коде. В этой статье разберем синтаксис, типовые примеры и критические нюансы, которые редко упоминаются в документации.

Что такое ТЗ в контексте запросов 1С

В классическом понимании техническое задание — это документ, описывающий требования к функционалу. Однако в под "ТЗ" часто подразумевают:

  • 📄 Структуру данных с условиями отбора (например, массив пар "Поле — Значение").
  • 🔧 Динамически формируемый текст запроса, где параметры подставляются в ГДЕ или УПОРЯДОЧИТЬ ПО.
  • 📊 Набор правил агрегации (например, "сгруппировать по контрагенту, если сумма больше X").

В запросах ТЗ может передаваться как:

  • 🔹 Простой параметр (например, &ТЗ в условии ГДЕ Номенклатура = &ТЗ.Номенклатура).
  • 🔹 Структура или массив, который разбирается в коде перед выполнением запроса.
  • 🔹 Строка с динамическим SQL (опасный подход, требующий защиты от инъекций!).
⚠️ Внимание: Если ТЗ формируется внешней системой (например, через HTTP-запрос), всегда валидируйте его содержимое. Передача необработанных данных в запрос может привести к SQL-инъекциям даже в управляемых формах .

Пример простейшего использования:

Запрос = Новый Запрос;

Запрос.Текст =

"ВЫБРАТЬ

| Номенклатура.Наименование,

| Номенклатура.Артикул

|ИЗ

| Справочник.Номенклатура КАК Номенклатура

|ГДЕ

| Номенклатура.Артикул = &Артикул";

Запрос.УстановитьПараметр("Артикул", ТЗ.Артикул); // ТЗ - структура с полем "Артикул"

Результат = Запрос.Выполнить();

📊 Как вы обычно передаете ТЗ в запросы 1С?
Через параметры запроса
Разбираю структуру в коде
Формирую динамический SQL
Использую временные таблицы

Синтаксис передачи ТЗ как параметра

Основной способ — использование именованных параметров с префиксом &. Однако есть нюансы:

1. Простые параметры

Подходят для передачи одиночных значений из ТЗ:

Запрос.Текст = "

ВЫБРАТЬ

Документ.Дата,

Документ.Номер

ИЗ

Документ.РеализацияТоваровУслуг КАК Документ

ГДЕ

Документ.Дата МЕЖДУ &ДатаНачала И &ДатаОкончания";

Запрос.УстановитьПараметр("ДатаНачала", ТЗ.Период.Начало);

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

2. Массивы и структуры

Если ТЗ содержит список значений (например, массив артикулов), используйте конструкцию В(&Параметр):

Запрос.Текст = "

ВЫБРАТЬ

Номенклатура.Наименование

ИЗ

Справочник.Номенклатура КАК Номенклатура

ГДЕ

Номенклатура.Артикул В(&СписокАртикулов)";

Запрос.УстановитьПараметр("СписокАртикулов", ТЗ.Артикулы); // ТЗ.Артикулы - массив

3. Динамический SQL (продвинутый уровень)

Для сложных условий можно формировать текст запроса на лету:

Условие = "";

Если ТЗ.ФильтрПоДате Тогда

Условие = Условие + " И Документ.Дата >= &ДатаНачала";

КонецЕсли;

Запрос.Текст = "

ВЫБРАТЬ

Документ.Номер

ИЗ

Документ.ЗаказПокупателя КАК Документ

ГДЕ" + Условие;

⚠️ Внимание: Динамический SQL уязвим для инъекций. Всегда экранируйте значения с помощью СокрЛП(Значение) или специализированных функций (например, ЭкранироватьСтроку() из библиотеки 1С:Библиотека стандартных подсистем).
Способ передачи ТЗ Пример кода Плюсы Минусы
Именованные параметры &Параметр Безопасно, просто Ограниченная гибкость
Массивы в В() В(&Массив) Удобно для списков Не работает с сложными условиями
Динамический SQL Строка с условиями Максимальная гибкость Риск инъекций, сложно поддерживать
💡

Для отладки динамических запросов используйте метод Запрос.ТекстЗапроса() после установки параметров — он покажет финальный SQL с подставленными значениями.

Практические примеры использования

Пример 1: Фильтрация по ТЗ из JSON

Допустим, внешняя система передает в JSON вида:

{

"Контрагенты": ["ООО Ромашка", "ИП Иванов"],

"Период": {

"С": "2023-01-01",

"По": "2023-12-31"

}

}

Разбираем его и формируем запрос:

ТЗ = JSON.Прочитать(ПолученныеДанные);

Запрос = Новый Запрос;

Запрос.Текст =

"ВЫБРАТЬ

| Документ.Контрагент,

| Документ.Сумма

|ИЗ

| Документ.РеализацияТоваровУслуг КАК Документ

|ГДЕ

| Документ.Контрагент В(&Контрагенты)

| И Документ.Дата МЕЖДУ &ДатаНачала И &ДатаОкончания";

Запрос.УстановитьПараметр("Контрагенты", ТЗ.Контрагенты);

Запрос.УстановитьПараметр("ДатаНачала", ТЗ.Период.С);

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

Пример 2: Универсальный отчет с ТЗ

Создадим отчет, где ТЗ определяет:

  • 📌 Поля выборки (например, только "Наименование" и "Цена").
  • 📌 Условия отбора (фильтр по складу, дате, статусу).
  • 📌 Группировку (сгруппировать по контрагенту или номенклатуре).
Процедура СформироватьОтчет(ТЗ)

Запрос = Новый Запрос;

// Динамически формируем список полей

Поля = "Документ.Номер";

Если ТЗ.ВключатьДату Тогда

Поля = Поля + ", Документ.Дата";

КонецЕсли;

// Динамически формируем условия

Условие = "";

Если ТЗ.ФильтрПоСкладу Тогда

Условие = Условие + " И Документ.Склад = &Склад";

КонецЕсли;

Запрос.Текст = "ВЫБРАТЬ " + Поля + "

ИЗ Документ.ЗаказПокупателя КАК Документ

ГДЕ Документ.ПометкаУдаления = ЛОЖЬ" + Условие;

// Устанавливаем параметры, если они есть

Если ТЗ.ФильтрПоСкладу Тогда

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

КонецЕсли;

Результат = Запрос.Выполнить();

КонецПроцедуры

Как защитить динамический запрос от SQL-инъекций?

Используйте метод ЭкранироватьСтроку() из БСП или замените опасные символы (кавычки, дефисы) на безопасные последовательности. Например:

Функция ЭкранироватьЗначение(Значение)

Возврат СтрЗаменить(СтрЗаменить(Значение, "'", "''"), "-", "_");

КонецФункции

Это предотвратит разрыв строки в SQL-запросе.

Типовые ошибки и как их избежать

Даже опытные разработчики допускают ошибки при работе с ТЗ в запросах. Рассмотрим самые распространенные:

1. Несоответствие типов данных

Если в ТЗ передается строка, а в запросе ожидается дата, не выдаст ошибку, но результат будет некорректным. Например:

// Ошибка: ТЗ.Дата - строка "2023-01-01", а нужно Дата

Запрос.УстановитьПараметр("Дата", ТЗ.Дата); // Не сработает как ожидается!

Решение: всегда преобразуйте данные к нужному типу:

Запрос.УстановитьПараметр("Дата", Дата(ТЗ.Дата));

2. Пустые параметры

Если поле в ТЗ не заполнено, условие с этим параметром может отсечь все записи. Например:

// Если ТЗ.Контрагент = Неопределено, то условие "Контрагент = NULL" вернет 0 строк

Запрос.Текст = "ГДЕ Контрагент = &Контрагент";

Решение: проверяйте параметры на Неопределено или ПустаяСтрока():

Если НЕ ЗначениеЗаполнено(ТЗ.Контрагент) Тогда

Запрос.Текст = СтрЗаменить(Запрос.Текст, "И Контрагент = &Контрагент", "");

Иначе

Запрос.УстановитьПараметр("Контрагент", ТЗ.Контрагент);

КонецЕсли;

3. Переполнение списков в В()

Конструкция В(&Массив) имеет ограничение на количество элементов (обычно ~1000). При превышении выдаст ошибку:

Ошибка при выполнении запроса: Слишком большой список значений в операторе IN

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

МассивЧастей = РазбитьМассив(ТЗ.Артикулы, 900); // По 900 элементов

Результат = Новый Массив;

Для Каждого Часть Из МассивЧастей Цикл

Запрос.УстановитьПараметр("Артикулы", Часть);

Результат.Добавить(Запрос.Выполнить());

КонецЦикла;

Проверьте типы данных параметров|Убедитесь, что нет пустых значений|Разбейте большие списки на части|Экранируйте строковые значения|Проверьте права доступа к данным-->

Производительность: как ускорить запросы с ТЗ

Динамические запросы с ТЗ часто работают медленнее статических. Оптимизируйте их с помощью этих приемов:

1. Используйте временные таблицы

Если ТЗ содержит большой список значений (например, 10 000 артикулов), вместо В(&Массив) загрузите данные во временную таблицу:

// Создаем временную таблицу

ВременнаяТаблица = Новый ТаблицаЗначений;

ВременнаяТаблица.Колонки.Добавить("Артикул");

Для Каждого Артикул Из ТЗ.Артикулы Цикл

Строка = ВременнаяТаблица.Добавить();

Строка.Артикул = Артикул;

КонецЦикла;

// Используем ее в запросе

Запрос.Текст = "

ВЫБРАТЬ

Номенклатура.Наименование

ИЗ

Справочник.Номенклатура КАК Номенклатура

ГДЕ

Номенклатура.Артикул В (

ВЫБРАТЬ

Артикул

ИЗ

&ВременнаяТаблица КАК ВТ

)";

Запрос.УстановитьПараметр("ВременнаяТаблица", ВременнаяТаблица);

2. Индексируйте поля в условиях

Если ТЗ содержит фильтр по полю, которое не проиндексировано (например, Номенклатура.ПолноеНаименование), запрос будет медленным. Проверьте индексы в конфигураторе:

  • 🔍 Откройте объект в конфигураторе.
  • 🔍 Перейдите на закладку Индексы.
  • 🔍 Добавьте индекс для полей, используемых в ТЗ.

3. Кэшируйте результаты

Если ТЗ меняется редко, кэшируйте результат запроса:

Процедура ПолучитьДанные(ТЗ)

КлючКэша = ПолучатьКлючКэша(ТЗ); // Например, хэш от сериализованного ТЗ

Если Кэш.Получить(КлючКэша, Результат) Тогда

Возврат Результат;

КонецЕсли;

// Выполняем запрос

Результат = ВыполнитьЗапросСТЗ(ТЗ);

Кэш.Поместить(КлючКэша, Результат, 3600); // Кэшируем на 1 час

Возврат Результат;

КонецПроцедуры

Проблема Решение Прирост скорости
Большой список в В() Временная таблица в 5-10 раз
Отсутствие индексов Добавить индекс в конфигураторе в 100+ раз
Повторные запросы с одинаковым ТЗ Кэширование в 10-100 раз
💡

Самый эффективный способ ускорить запрос с большим списком значений — замена В(&Массив) на временную таблицу. Это снижает нагрузку на СУБД и устраняет ограничение на количество элементов.

Безопасность: защита от SQL-инъекций

Передача ТЗ из внешних источников (например, из веб-формы или API) требует особого внимания к безопасности. Злоумышленник может подсунуть в ТЗ вредоносный SQL-код, который выполнится на сервере.

1. Экранирование строк

Все строковые параметры из ТЗ должны экранироваться. В для этого нет встроенной функции, поэтому используйте:

Функция ЭкранироватьСтроку(Строка)

Возврат СтрЗаменить(Строка, "'", "''");

КонецФункции

// Пример использования

Запрос.УстановитьПараметр("Наименование", ЭкранироватьСтроку(ТЗ.Наименование));

2. Белый список полей

Если ТЗ определяет, по каким полям фильтровать данные, ограничьте список разрешенных полей:

РазрешенныеПоля = Новый Массив;

РазрешенныеПоля.Добавить("Наименование");

РазрешенныеПоля.Добавить("Артикул");

РазрешенныеПоля.Добавить("Дата");

Если НЕ РазрешенныеПоля.Найти(ТЗ.ПолеФильтра) Тогда

ВызватьИсключение "Поле '" + ТЗ.ПолеФильтра + "' не разрешено для фильтрации";

КонецЕсли;

3. Параметризованные запросы

Никогда не подставляйте значения из ТЗ напрямую в текст запроса:

// ОПАСНО!

Запрос.Текст = "ГДЕ Наименование = '" + ТЗ.Наименование + "'"; // Уязвимость для SQL-инъекции

// БЕЗОПАСНО

Запрос.Текст = "ГДЕ Наименование = &Наименование";

Запрос.УстановитьПараметр("Наименование", ТЗ.Наименование);

⚠️ Внимание: Даже если вы используете параметризованные запросы, всегда валидируйте структуру ТЗ. Например, проверяйте, что передаваемые даты корректны, а числовые значения не выходят за разумные пределы (например, ТЗ.Сумма > 1e9 может быть признаком атаки).

SQL-инъекции в 1С: мифы и реальность

Многие разработчики считают, что защищена от SQL-инъекций благодаря абстракции языка запросов. Это не так. Уязвимости возникают в следующих случаях:

1. Динамический SQL

Если вы формируете текст запроса на лету, подставляя значения из ТЗ, риск инъекции высок. Пример атаки:

// ТЗ.Фильтр = "1' OR 1=1 --"

Запрос.Текст = "ГДЕ Номер = '" + ТЗ.Фильтр + "'";

// Результирующий запрос:

// ГДЕ Номер = '1' OR 1=1 --' (вернет все записи!)

2. Выполнение произвольного кода

В 1С 8.3 есть возможность выполнять произвольный SQL через ВыполнитьТекстовыйЗапрос() (в режиме отладки). Если ТЗ формируется внешней системой, злоумышленник может передать:

{

"Запрос": "ДОБАВИТЬ ЗНАЧЕНИЕ В РегистрСведений.Пользователи (Пользователь = 'admin', Пароль = '123')"

}

3. Обход прав доступа

Даже если инъекция не разрушит базу, она может обойти права доступа. Например, запрос:

ВЫБРАТЬ

Пользователи.Имя,

Пользователи.Пароль

ИЗ

v8users

может вернуть хэши паролей пользователей, если ТЗ сформировано с соответствующим условием.

Как защититься:

  • 🔒 Используйте только параметризованные запросы.
  • 🔒 Ограничивайте права пользователя, от имени которого выполняется запрос.
  • 🔒 Ведите лог всех динамических запросов (например, в регистр сведений).
Пример атаки через ТЗ в реальной системе

В одной из компаний злоумышленник передал в ТЗ условие "1=1; ДОБАВИТЬ ЗНАЧЕНИЕ В РегистрСведений.Скидки (Контрагент = 'ООО Ромашка', Процент = 99)--", что привело к автоматическому применению скидки 99% для всех заказов этого контрагента. Убытки составили более 2 млн рублей.

Альтернативные подходы: когда ТЗ в запросе не нужен

Иногда передача ТЗ напрямую в запрос избыточна. Рассмотрите альтернативы:

1. Предварительная обработка данных

Если ТЗ содержит сложную логику (например, проверку остатков на складах с учетом резервов), лучше:

  • 📊 Выгрузить исходные данные в таблицу значений.
  • 📊 Обработать их на встроенном языке .
  • 📊 Отфильтровать по критериям из ТЗ.

Это медленнее для больших объемов, но безопаснее и гибче.

2. Хранимые процедуры

В можно вызывать хранимые процедуры СУБД (PostgreSQL, MS SQL). Если ТЗ стандартизирован, перенесите логику фильтрации в процедуру:

Запрос = Новый Запрос;

Запрос.Текст = "ВЫЗВАТЬ ХранимаяПроцедура_ФильтрПоТЗ(&Параметр1, &Параметр2)";

Запрос.УстановитьПараметр("Параметр1", ТЗ.Параметр1);

Результат = Запрос.Выполнить();

3. Виртуальные таблицы

Для отчетов с группировками и агрегациями используйте виртуальные таблицы (например, РегистрНакопления.Остатки.ОстаткиИОбороты). Они оптимизированы на уровне платформы и не требуют ручной обработки ТЗ:

Запрос.Текст = "

ВЫБРАТЬ

Остатки.Номенклатура,

СУММА(Остатки.КоличествоОстаток) КАК Остаток

ИЗ

РегистрНакопления.ТоварыНаСкладах.Остатки(&ДатаОстатков,) КАК Остатки

ГДЕ

Остатки.Склад = &Склад";

Запрос.УстановитьПараметр("ДатаОстатков", ТЗ.Дата);

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

FAQ: Частые вопросы по ТЗ в запросах 1С

Можно ли передавать в запрос целый объект ТЗ (например, структуру)?

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

  • Примитивные типы (Число, Строка, Дата).
  • Массивы примитивных типов (для использования в В(&Массив)).
  • Таблицы значений (для временных таблиц).

Если ТЗ — это структура, разберите ее на отдельные параметры перед передачей в запрос.

Как передать в запрос условие с оператором ПОДОБНО из ТЗ?

Используйте параметр для шаблона, но экранируйте специальные символы (%, _):

Шаблон = СтрЗаменить(ТЗ.Поиск, "%", "[%]");

Шаблон = СтрЗаменить(Шаблон, "_", "[_]");

Запрос.Текст = "ГДЕ Наименование ПОДОБНО &Шаблон";

Запрос.УстановитьПараметр("Шаблон", Шаблон);

Пример: если ТЗ.Поиск = "това%р", то после экранирования шаблон станет "тов[a%]р".

Почему запрос с ТЗ работает медленно, хотя индексы есть?

Возможные причины:

  1. Слишком широкие условия (например, ПОДОБНО '%текст%' не использует индексы).
  2. Большой список в В() (разбейте на части или используйте временную таблицу).
  3. Неоптимальный план выполнения (проверьте в консоли СУБД с помощью EXPLAIN).
  4. Блокировки (если ТЗ фильтрует данные, которые активно изменяются другими пользователями).

Используйте ОбъяснитьЗапрос() для анализа:

Объяснение = Запрос.Объяснить();

Сообщить(Объяснение.ПолучитьТекст());

Как передать в запрос иерархический фильтр (например, по группам номенклатуры)?

Для фильтрации по иерархии используйте:

  1. Временную таблицу с предварительно развернутой иерархией.
  2. Функцию ПутьКДанным() (для справочников с иерархией):

Запрос.Текст = "

ВЫБРАТЬ

Номенклатура.Ссылка

ИЗ

Справочник.Номенклатура КАК Номенклатура

ГДЕ

Номенклатура.ПутьКДанным ПОДОБНО &Путь";

Путь = ".%." + ТЗ.Группа + ".%";

Запрос.УстановитьПараметр("Путь", Путь);

Можно ли использовать ТЗ для динамического формирования полей выборки?

Да, но это требует формирования текста запроса на лету. Пример:

Поля = "Документ.Номер";

Если ТЗ.ВключатьСумму Тогда

Поля = Поля + ", Документ.СуммаДокумента";

КонецЕсли;

Запрос.Текст = "ВЫБРАТЬ " + Поля + "

ИЗ Документ