В системе 1С:Предприятие параметры запросов позволяют динамически формировать SQL-подобные конструкции, адаптируя их под конкретные задачи. Одним из неочевидных, но крайне полезных применений является передача технического задания (ТЗ) как параметра. Этот подход актуален при работе с большими объемами данных, когда необходимо гибко фильтровать записи по критериям, заданным в ТЗ, или когда логика отбора слишком сложна для стандартных конструкций языка запросов.
На практике такой механизм востребован в сценариях интеграции, когда внешние системы передают в 1С структурированные условия отбора (например, через JSON или XML), а также при разработке универсальных отчетов, где пользователь может задавать произвольные фильтры. Однако неправильное использование ТЗ как параметра чревато ошибками выполнения, проблемами с производительностью или даже уязвимостями в коде. В этой статье разберем синтаксис, типовые примеры и критические нюансы, которые редко упоминаются в документации.
Что такое ТЗ в контексте запросов 1С
В классическом понимании техническое задание — это документ, описывающий требования к функционалу. Однако в 1С под "ТЗ" часто подразумевают:
- 📄 Структуру данных с условиями отбора (например, массив пар "Поле — Значение").
- 🔧 Динамически формируемый текст запроса, где параметры подставляются в
ГДЕилиУПОРЯДОЧИТЬ ПО. - 📊 Набор правил агрегации (например, "сгруппировать по контрагенту, если сумма больше X").
В запросах 1С ТЗ может передаваться как:
- 🔹 Простой параметр (например,
&ТЗв условииГДЕ Номенклатура = &ТЗ.Номенклатура). - 🔹 Структура или массив, который разбирается в коде перед выполнением запроса.
- 🔹 Строка с динамическим SQL (опасный подход, требующий защиты от инъекций!).
⚠️ Внимание: Если ТЗ формируется внешней системой (например, через HTTP-запрос), всегда валидируйте его содержимое. Передача необработанных данных в запрос может привести к SQL-инъекциям даже в управляемых формах 1С.
Пример простейшего использования:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Номенклатура.Наименование,
| Номенклатура.Артикул
|ИЗ
| Справочник.Номенклатура КАК Номенклатура
|ГДЕ
| Номенклатура.Артикул = &Артикул";
Запрос.УстановитьПараметр("Артикул", ТЗ.Артикул); // ТЗ - структура с полем "Артикул"
Результат = Запрос.Выполнить();
Синтаксис передачи ТЗ как параметра
Основной способ — использование именованных параметров с префиксом &. Однако есть нюансы:
1. Простые параметры
Подходят для передачи одиночных значений из ТЗ:
Запрос.Текст = "
ВЫБРАТЬ
Документ.Дата,
Документ.Номер
ИЗ
Документ.РеализацияТоваровУслуг КАК Документ
ГДЕ
Документ.Дата МЕЖДУ &ДатаНачала И &ДатаОкончания";
Запрос.УстановитьПараметр("ДатаНачала", ТЗ.Период.Начало);
Запрос.УстановитьПараметр("ДатаОкончания", ТЗ.Период.Конец);
2. Массивы и структуры
Если ТЗ содержит список значений (например, массив артикулов), используйте конструкцию В(&Параметр):
Запрос.Текст = "
ВЫБРАТЬ
Номенклатура.Наименование
ИЗ
Справочник.Номенклатура КАК Номенклатура
ГДЕ
Номенклатура.Артикул В(&СписокАртикулов)";
Запрос.УстановитьПараметр("СписокАртикулов", ТЗ.Артикулы); // ТЗ.Артикулы - массив
3. Динамический SQL (продвинутый уровень)
Для сложных условий можно формировать текст запроса на лету:
Условие = "";
Если ТЗ.ФильтрПоДате Тогда
Условие = Условие + " И Документ.Дата >= &ДатаНачала";
КонецЕсли;
Запрос.Текст = "
ВЫБРАТЬ
Документ.Номер
ИЗ
Документ.ЗаказПокупателя КАК Документ
ГДЕ" + Условие;
⚠️ Внимание: Динамический SQL уязвим для инъекций. Всегда экранируйте значения с помощьюСокрЛП(Значение)или специализированных функций (например,ЭкранироватьСтроку()из библиотеки 1С:Библиотека стандартных подсистем).
| Способ передачи ТЗ | Пример кода | Плюсы | Минусы |
|---|---|---|---|
| Именованные параметры | &Параметр |
Безопасно, просто | Ограниченная гибкость |
Массивы в В() |
В(&Массив) |
Удобно для списков | Не работает с сложными условиями |
| Динамический SQL | Строка с условиями | Максимальная гибкость | Риск инъекций, сложно поддерживать |
Для отладки динамических запросов используйте метод Запрос.ТекстЗапроса() после установки параметров — он покажет финальный SQL с подставленными значениями.
Практические примеры использования
Пример 1: Фильтрация по ТЗ из JSON
Допустим, внешняя система передает в 1С JSON вида:
{
"Контрагенты": ["ООО Ромашка", "ИП Иванов"],
"Период": {
"С": "2023-01-01",
"По": "2023-12-31"
}
}
Разбираем его и формируем запрос:
ТЗ = JSON.Прочитать(ПолученныеДанные);
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Документ.Контрагент,
| Документ.Сумма
|ИЗ
| Документ.РеализацияТоваровУслуг КАК Документ
|ГДЕ
| Документ.Контрагент В(&Контрагенты)
| И Документ.Дата МЕЖДУ &ДатаНачала И &ДатаОкончания";
Запрос.УстановитьПараметр("Контрагенты", ТЗ.Контрагенты);
Запрос.УстановитьПараметр("ДатаНачала", ТЗ.Период.С);
Запрос.УстановитьПараметр("ДатаОкончания", ТЗ.Период.По);
Пример 2: Универсальный отчет с ТЗ
Создадим отчет, где ТЗ определяет:
- 📌 Поля выборки (например, только "Наименование" и "Цена").
- 📌 Условия отбора (фильтр по складу, дате, статусу).
- 📌 Группировку (сгруппировать по контрагенту или номенклатуре).
Процедура СформироватьОтчет(ТЗ)
Запрос = Новый Запрос;
// Динамически формируем список полей
Поля = "Документ.Номер";
Если ТЗ.ВключатьДату Тогда
Поля = Поля + ", Документ.Дата";
КонецЕсли;
// Динамически формируем условия
Условие = "";
Если ТЗ.ФильтрПоСкладу Тогда
Условие = Условие + " И Документ.Склад = &Склад";
КонецЕсли;
Запрос.Текст = "ВЫБРАТЬ " + Поля + "
ИЗ Документ.ЗаказПокупателя КАК Документ
ГДЕ Документ.ПометкаУдаления = ЛОЖЬ" + Условие;
// Устанавливаем параметры, если они есть
Если ТЗ.ФильтрПоСкладу Тогда
Запрос.УстановитьПараметр("Склад", ТЗ.Склад);
КонецЕсли;
Результат = Запрос.Выполнить();
КонецПроцедуры
Как защитить динамический запрос от SQL-инъекций?
Используйте метод ЭкранироватьСтроку() из БСП или замените опасные символы (кавычки, дефисы) на безопасные последовательности. Например:
Функция ЭкранироватьЗначение(Значение)
Возврат СтрЗаменить(СтрЗаменить(Значение, "'", "''"), "-", "_");
КонецФункции
Это предотвратит разрыв строки в SQL-запросе.
Типовые ошибки и как их избежать
Даже опытные разработчики допускают ошибки при работе с ТЗ в запросах. Рассмотрим самые распространенные:
1. Несоответствие типов данных
Если в ТЗ передается строка, а в запросе ожидается дата, 1С не выдаст ошибку, но результат будет некорректным. Например:
// Ошибка: ТЗ.Дата - строка "2023-01-01", а нужно Дата
Запрос.УстановитьПараметр("Дата", ТЗ.Дата); // Не сработает как ожидается!
Решение: всегда преобразуйте данные к нужному типу:
Запрос.УстановитьПараметр("Дата", Дата(ТЗ.Дата));
2. Пустые параметры
Если поле в ТЗ не заполнено, условие с этим параметром может отсечь все записи. Например:
// Если ТЗ.Контрагент = Неопределено, то условие "Контрагент = NULL" вернет 0 строк
Запрос.Текст = "ГДЕ Контрагент = &Контрагент";
Решение: проверяйте параметры на Неопределено или ПустаяСтрока():
Если НЕ ЗначениеЗаполнено(ТЗ.Контрагент) Тогда
Запрос.Текст = СтрЗаменить(Запрос.Текст, "И Контрагент = &Контрагент", "");
Иначе
Запрос.УстановитьПараметр("Контрагент", ТЗ.Контрагент);
КонецЕсли;
3. Переполнение списков в В()
Конструкция В(&Массив) имеет ограничение на количество элементов (обычно ~1000). При превышении 1С выдаст ошибку:
Ошибка при выполнении запроса: Слишком большой список значений в операторе IN
Решение: разбивайте большой массив на части и выполняйте несколько запросов:
МассивЧастей = РазбитьМассив(ТЗ.Артикулы, 900); // По 900 элементов
Результат = Новый Массив;
Для Каждого Часть Из МассивЧастей Цикл
Запрос.УстановитьПараметр("Артикулы", Часть);
Результат.Добавить(Запрос.Выполнить());
КонецЦикла;
Проверьте типы данных параметров|Убедитесь, что нет пустых значений|Разбейте большие списки на части|Экранируйте строковые значения|Проверьте права доступа к данным-->
Производительность: как ускорить запросы с ТЗ
Динамические запросы с ТЗ часто работают медленнее статических. Оптимизируйте их с помощью этих приемов:
1. Используйте временные таблицы
Если ТЗ содержит большой список значений (например, 10 000 артикулов), вместо В(&Массив) загрузите данные во временную таблицу:
// Создаем временную таблицу
ВременнаяТаблица = Новый ТаблицаЗначений;
ВременнаяТаблица.Колонки.Добавить("Артикул");
Для Каждого Артикул Из ТЗ.Артикулы Цикл
Строка = ВременнаяТаблица.Добавить();
Строка.Артикул = Артикул;
КонецЦикла;
// Используем ее в запросе
Запрос.Текст = "
ВЫБРАТЬ
Номенклатура.Наименование
ИЗ
Справочник.Номенклатура КАК Номенклатура
ГДЕ
Номенклатура.Артикул В (
ВЫБРАТЬ
Артикул
ИЗ
&ВременнаяТаблица КАК ВТ
)";
Запрос.УстановитьПараметр("ВременнаяТаблица", ВременнаяТаблица);
2. Индексируйте поля в условиях
Если ТЗ содержит фильтр по полю, которое не проиндексировано (например, Номенклатура.ПолноеНаименование), запрос будет медленным. Проверьте индексы в конфигураторе:
- 🔍 Откройте объект в конфигураторе.
- 🔍 Перейдите на закладку
Индексы. - 🔍 Добавьте индекс для полей, используемых в ТЗ.
3. Кэшируйте результаты
Если ТЗ меняется редко, кэшируйте результат запроса:
Процедура ПолучитьДанные(ТЗ)
КлючКэша = ПолучатьКлючКэша(ТЗ); // Например, хэш от сериализованного ТЗ
Если Кэш.Получить(КлючКэша, Результат) Тогда
Возврат Результат;
КонецЕсли;
// Выполняем запрос
Результат = ВыполнитьЗапросСТЗ(ТЗ);
Кэш.Поместить(КлючКэша, Результат, 3600); // Кэшируем на 1 час
Возврат Результат;
КонецПроцедуры
| Проблема | Решение | Прирост скорости |
|---|---|---|
Большой список в В() |
Временная таблица | в 5-10 раз |
| Отсутствие индексов | Добавить индекс в конфигураторе | в 100+ раз |
| Повторные запросы с одинаковым ТЗ | Кэширование | в 10-100 раз |
Самый эффективный способ ускорить запрос с большим списком значений — замена В(&Массив) на временную таблицу. Это снижает нагрузку на СУБД и устраняет ограничение на количество элементов.
Безопасность: защита от SQL-инъекций
Передача ТЗ из внешних источников (например, из веб-формы или API) требует особого внимания к безопасности. Злоумышленник может подсунуть в ТЗ вредоносный SQL-код, который выполнится на сервере.
1. Экранирование строк
Все строковые параметры из ТЗ должны экранироваться. В 1С для этого нет встроенной функции, поэтому используйте:
Функция ЭкранироватьСтроку(Строка)
Возврат СтрЗаменить(Строка, "'", "''");
КонецФункции
// Пример использования
Запрос.УстановитьПараметр("Наименование", ЭкранироватьСтроку(ТЗ.Наименование));
2. Белый список полей
Если ТЗ определяет, по каким полям фильтровать данные, ограничьте список разрешенных полей:
РазрешенныеПоля = Новый Массив;
РазрешенныеПоля.Добавить("Наименование");
РазрешенныеПоля.Добавить("Артикул");
РазрешенныеПоля.Добавить("Дата");
Если НЕ РазрешенныеПоля.Найти(ТЗ.ПолеФильтра) Тогда
ВызватьИсключение "Поле '" + ТЗ.ПолеФильтра + "' не разрешено для фильтрации";
КонецЕсли;
3. Параметризованные запросы
Никогда не подставляйте значения из ТЗ напрямую в текст запроса:
// ОПАСНО!
Запрос.Текст = "ГДЕ Наименование = '" + ТЗ.Наименование + "'"; // Уязвимость для SQL-инъекции
// БЕЗОПАСНО
Запрос.Текст = "ГДЕ Наименование = &Наименование";
Запрос.УстановитьПараметр("Наименование", ТЗ.Наименование);
⚠️ Внимание: Даже если вы используете параметризованные запросы, всегда валидируйте структуру ТЗ. Например, проверяйте, что передаваемые даты корректны, а числовые значения не выходят за разумные пределы (например, ТЗ.Сумма > 1e9 может быть признаком атаки).
SQL-инъекции в 1С: мифы и реальность
Многие разработчики считают, что 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. Предварительная обработка данных
Если ТЗ содержит сложную логику (например, проверку остатков на складах с учетом резервов), лучше:
- 📊 Выгрузить исходные данные в таблицу значений.
- 📊 Обработать их на встроенном языке 1С.
- 📊 Отфильтровать по критериям из ТЗ.
Это медленнее для больших объемов, но безопаснее и гибче.
2. Хранимые процедуры
В 1С можно вызывать хранимые процедуры СУБД (PostgreSQL, MS SQL). Если ТЗ стандартизирован, перенесите логику фильтрации в процедуру:
Запрос = Новый Запрос;
Запрос.Текст = "ВЫЗВАТЬ ХранимаяПроцедура_ФильтрПоТЗ(&Параметр1, &Параметр2)";
Запрос.УстановитьПараметр("Параметр1", ТЗ.Параметр1);
Результат = Запрос.Выполнить();
3. Виртуальные таблицы
Для отчетов с группировками и агрегациями используйте виртуальные таблицы (например, РегистрНакопления.Остатки.ОстаткиИОбороты). Они оптимизированы на уровне платформы и не требуют ручной обработки ТЗ:
Запрос.Текст = "
ВЫБРАТЬ
Остатки.Номенклатура,
СУММА(Остатки.КоличествоОстаток) КАК Остаток
ИЗ
РегистрНакопления.ТоварыНаСкладах.Остатки(&ДатаОстатков,) КАК Остатки
ГДЕ
Остатки.Склад = &Склад";
Запрос.УстановитьПараметр("ДатаОстатков", ТЗ.Дата);
Запрос.УстановитьПараметр("Склад", ТЗ.Склад);
FAQ: Частые вопросы по ТЗ в запросах 1С
Можно ли передавать в запрос целый объект ТЗ (например, структуру)?
Нет, в запросах 1С нельзя передавать сложные объекты (структуры, массивы объектов) как параметры. Можно передавать только:
- Примитивные типы (
Число,Строка,Дата). - Массивы примитивных типов (для использования в
В(&Массив)). - Таблицы значений (для временных таблиц).
Если ТЗ — это структура, разберите ее на отдельные параметры перед передачей в запрос.
Как передать в запрос условие с оператором ПОДОБНО из ТЗ?
Используйте параметр для шаблона, но экранируйте специальные символы (%, _):
Шаблон = СтрЗаменить(ТЗ.Поиск, "%", "[%]");
Шаблон = СтрЗаменить(Шаблон, "_", "[_]");
Запрос.Текст = "ГДЕ Наименование ПОДОБНО &Шаблон";
Запрос.УстановитьПараметр("Шаблон", Шаблон);
Пример: если ТЗ.Поиск = "това%р", то после экранирования шаблон станет "тов[a%]р".
Почему запрос с ТЗ работает медленно, хотя индексы есть?
Возможные причины:
- Слишком широкие условия (например,
ПОДОБНО '%текст%'не использует индексы). - Большой список в
В()(разбейте на части или используйте временную таблицу). - Неоптимальный план выполнения (проверьте в консоли СУБД с помощью
EXPLAIN). - Блокировки (если ТЗ фильтрует данные, которые активно изменяются другими пользователями).
Используйте ОбъяснитьЗапрос() для анализа:
Объяснение = Запрос.Объяснить();
Сообщить(Объяснение.ПолучитьТекст());
Как передать в запрос иерархический фильтр (например, по группам номенклатуры)?
Для фильтрации по иерархии используйте:
- Временную таблицу с предварительно развернутой иерархией.
- Функцию
ПутьКДанным()(для справочников с иерархией):
Запрос.Текст = "
ВЫБРАТЬ
Номенклатура.Ссылка
ИЗ
Справочник.Номенклатура КАК Номенклатура
ГДЕ
Номенклатура.ПутьКДанным ПОДОБНО &Путь";
Путь = ".%." + ТЗ.Группа + ".%";
Запрос.УстановитьПараметр("Путь", Путь);
Можно ли использовать ТЗ для динамического формирования полей выборки?
Да, но это требует формирования текста запроса на лету. Пример:
Поля = "Документ.Номер";
Если ТЗ.ВключатьСумму Тогда
Поля = Поля + ", Документ.СуммаДокумента";
КонецЕсли;
Запрос.Текст = "ВЫБРАТЬ " + Поля + "
ИЗ Документ