При работе с механизмами запросов в платформе 1С:Предприятие разработчики часто сталкиваются с необходимостью передавать внешние таблицы значений (ТЗ) в качестве параметров. Ситуация усложняется, когда структура этой таблицы заранее неизвестна, и список выбираемых полей требуется формировать динамически на лету. Попытка собрать текст запроса вручную через банальное сложение строк часто приводит к синтаксическим ошибкам СУБД (например, из-за лишней запятой в конце списка полей или опечаток в конструкции).
В этой статье мы подробно разберем, как правильно реализовать динамический обход колонок таблицы значений, как избежать распространенных синтаксических ошибок, а также рассмотрим важные архитектурные нюансы — от обязательной типизации колонок до оптимизации производительности при левых соединениях.
Рассмотрим типичный код начинающего разработчика, который пытается обойти массив имен колонок и добавить их в секцию ВЫБРАТЬ:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
|";
Для Каждого СтИмя ИЗ МассивИменСтолбцов Цикл
Запрос.Текст = Запрос.Текст + "
| Таблица1." + СтИмя + " КАК " + СтИмя + ","
КонецЦикла;
Запрос.Текст = Запрос.Текст + "
|ПОМЕСТИТЬ ВТ1
|ИЗ
| &Таблица1 КАК Таблица1";
В данном коде кроются сразу две критические проблемы:
",". В результате текст запроса перед ключевым словом ПОМЕСТИТЬ или ИЗ будет заканчиваться запятой, что вызовет синтаксическую ошибку СУБД: «Ожидается выражение».ТеЗапрос.Тексткст, что привело к ошибке компиляции еще на этапе проверки конфигурации.Давайте разберем надежные и изящные способы решения этой задачи.
Самый простой способ избежать лишней запятой — накапливать список полей в отдельную строковую переменную и добавлять запятую только перед последующими элементами (начиная со второго). Проанализируем пример кода:
СтрокаПолей = "";
Для Каждого СтИмя ИЗ МассивИменСтолбцов Цикл
СтрокаПолей = СтрокаПолей + ?(СтрокаПолей = "", "", ", ") + "Таблица1." + СтИмя + " КАК " + СтИмя;
КонецЦикла;
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ " + СтрокаПолей + "
|ПОМЕСТИТЬ ВТ1
|ИЗ
| &Таблица1 КАК Таблица1";
В данном примере тернарный оператор ?() проверяет, пуста ли переменная СтрокаПолей. Если она пуста (это первая итерация), то разделитель не добавляется. Для всех последующих колонок перед добавлением нового имени будет автоматически вставляться запятая с пробелом. Этот метод надежен и легко читается.
Начиная с версии платформы 1С 8.3.6, у разработчиков появилась встроенная функция СтрСоединить(). Она позволяет полностью отказаться от циклов при формировании списков для запроса. Это делает код лаконичным, производительным и снижает вероятность совершить ошибку.
Посмотрим на пример реализации:
// Формируем массив полей с префиксами
МассивПолейСПрефиксом = Новый Массив;
Для Каждого ИмяКолонки Из МассивИменСтолбцов Цикл
МассивПолейСПрефиксом.Добавить("Таблица1." + ИмяКолонки + " КАК " + ИмяКолонки);
КонецЦикла;
// Соединяем элементы массива через запятую
СтрокаПолей = СтрСоединить(МассивПолейСПрефиксом, ", ");
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ " + СтрокаПолей + "
|ПОМЕСТИТЬ ВТ1
|ИЗ
| &Таблица1 КАК Таблица1";
Функция СтрСоединить() самостоятельно склеит все элементы массива, вставив разделитель ", " строго МЕЖДУ элементами. В конце результирующей строки никакой лишней запятой не окажется.
Когда текст запроса достаточно объемный, прямая склейка строк через оператор "+" делает код трудночитаемым. Чтобы визуально отделить структуру (шаблон) запроса от динамически подставляемых полей, рекомендуется использовать функцию СтрШаблон(). Разберем этот подход на практике:
ШаблонЗапроса =
"ВЫБРАТЬ
| %1
|ПОМЕСТИТЬ ВТ1
|ИЗ
| &Таблица1 КАК Таблица1";
// Собираем поля
СтрокаПолей = "Таблица1." + СтрСоединить(МассивИменСтолбцов, ", Таблица1.");
// Подставляем сформированную строку вместо параметра %1
Запрос = Новый Запрос;
Запрос.Текст = СтрШаблон(ШаблонЗапроса, СтрокаПолей);
Этот подход признан хорошим тоном разработки в среде 1С, так как текст запроса остается структурированным и его легко переносить в консоль запросов для отладки.
Просто собрать текст запроса недостаточно. Чтобы код работал стабильно и не вызывал исключений во время сеанса пользователей, необходимо соблюдать три важнейших правила платформы 1С.
Если вы создаете таблицу значений программно и планируете передать ее в качестве параметра запроса, **каждая колонка должна иметь явно заданный тип**. Если добавить колонку без указания типа:
// ОШИБКА! В запросе такая колонка вызовет сбой
ТЗ.Колонки.Добавить("Сумма");
При попытке выполнить запрос платформа сгенерирует критическую ошибку: «Тип не может быть выбран в запросе». Это происходит потому, что СУБД должна заранее знать типы и размеры полей для построения оптимального плана выполнения.
Правильный способ инициализации колонок выглядит так:
ОписаниеТипаЧисло = Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(15, 2));
ОписаниеТипаСтрока = Новый ОписаниеТипов("Строка", Новый КвалификаторыСтроки(150));
ТЗ = Новый ТаблицаЗначений;
ТЗ.Колонки.Добавить("Номенклатура", Новый ОписаниеТипов("СправочникСсылка.Номенклатура"));
ТЗ.Колонки.Добавить("Количество", ОписаниеТипаЧисло);
ТЗ.Колонки.Добавить("Комментарий", ОписаниеТипаСтрока);
Как упоминал автор темы на форуме, далее планируется выполнять левое соединение динамической таблицы с другими таблицами базы данных по множеству полей. Если в вашей временной таблице содержится более нескольких десятков строк, отсутствие индексов по полям связи гарантированно приведет к деградации производительности СУБД.
При динамическом формировании текста запроса обязательно добавляйте инструкцию ИНДЕКСИРОВАТЬ ПО. Вы можете сформировать список индексируемых полей так же динамически:
МассивПолейСвязи = Новый Массив;
МассивПолейСвязи.Добавить("Номенклатура");
МассивПолейСвязи.Добавить("Склад");
СтрокаИндексов = СтрСоединить(МассивПолейСвязи, ", ");
ШаблонЗапроса =
"ВЫБРАТЬ
| %1
|ПОМЕСТИТЬ ВТ_Данные
|ИЗ
| &Таблица1 КАК Таблица1
|
|ИНДЕКСИРОВАТЬ ПО
| %2";
Запрос.Текст = СтрШаблон(ШаблонЗапроса, СтрокаПолей, СтрокаИндексов);
Индексирование полей связи существенно ускорит операцию ЛЕВОЕ СОЕДИНЕНИЕ на уровне СУБД.
Некоторые участники форума советуют использовать конструкцию вида Таблица1.* для выбора всех полей. Хотя это кажется простым решением, у него есть существенные недостатки:
Поэтому явное перечисление необходимых колонок («КАК ИмяПоля») всегда предпочтительнее «звездочки».
Если логика формирования вашего запроса слишком сложная (содержит много условий, ветвлений и динамических соединений), вместо ручного конструирования строк имеет смысл использовать специализированный объект платформы — СхемаЗапроса.
Схема запроса представляет собой объектную модель. Вы можете добавлять пакеты, выбирать таблицы, создавать связи и условия, работая с коллекциями платформы, а не со строками кода. Платформа сама сгенерирует валидный SQL-подобный текст запроса, расставит скобки, запятые и ключевые слова.
Разберем краткий пример программного добавления поля в выборку через схему:
Схема = Новый СхемаЗапроса;
Пакет1 = Схема.ПакетЗапросов[0]; // Первый запрос в пакете
ОператорВыбора = Пакет1.Операторы[0];
// Добавляем таблицу-источник
Источник = ОператорВыбора.Источники.Добавить("ВТ_Данные", "ТаблицаДанных");
// Динамически добавляем поля в выборку
Для Каждого ИмяКолонки Из МассивИменСтолбцов Цикл
Пакет1.Колонки.Добавить("ТаблицаДанных." + ИмяКолонки);
КонецЦикла;
// Получаем готовый и синтаксически корректный текст запроса
ТекстЗапроса = Схема.ПолучитьТекстЗапроса();
Использование СхемаЗапроса полностью исключает синтаксические ошибки ручного построения строк, однако требует более глубокого понимания объектной модели запросов 1С.