Работа с таблицами значений в 1С:Предприятие часто требует их интеграции с SQL-запросами — будь то фильтрация данных, массовая обработка или сложные отчеты. Однако стандартный механизм параметров запроса УстановитьПараметр() не поддерживает прямую передачу таблиц как аргументов. Это ограничение заставляет разработчиков искать обходные пути, каждый из которых имеет свои нюансы производительности, безопасности и удобства.
В этой статье мы разберем три основных подхода к решению задачи — от использования временных таблиц до преобразования данных в строковые массивы, — а также проанализируем их плюсы и минусы на реальных примерах. Особое внимание уделим типичным ошибкам, которые приводят к падению производительности или некорректным результатам, и покажем, как их избежать. Если вы когда-нибудь сталкивались с сообщением об ошибке "Недопустимый тип параметра запроса", этот материал поможет разобраться в причинах и найти оптимальное решение для вашей задачи.
Почему нельзя передать таблицу значений напрямую?
Архитектура 1С:Предприятие 8 изначально не предусматривает передачу сложных типов данных (таких как ТаблицаЗначений, ДеревоЗначений или Соответствие) в параметры запроса. Это ограничение обусловлено несколькими факторами:
- 🔹 Типизация SQL: Язык запросов 1С опирается на SQL-синтаксис, где параметры должны быть скалярными значениями (число, строка, дата) или коллекциями фиксированного формата (например, массив чисел).
- 🔹 Производительность: Передача целой таблицы как параметра потребовала бы сериализации данных, что могло бы замедлить выполнение запроса, особенно при больших объемах.
- 🔹 Безопасность: Риск SQL-инъекций возрастает при динамическом формировании запросов на основе структурированных данных.
В результате разработчикам приходится использовать косвенные методы, каждый из которых решает задачу по-своему. Например, временные таблицы подходят для сложных фильтров, а строковые массивы — для простых списков значений. Выбор метода зависит от контекста: объема данных, требований к скорости и особенностей СУБД (например, Microsoft SQL Server или PostgreSQL).
Метод 1: Использование временных таблиц
Самый универсальный и производительный способ — создание временной таблицы в базе данных, заполнение её данными из таблицы значений, а затем обращение к ней в основном запросе. Этот подход особенно эффективен для больших объемов данных (тысячи строк) и сложных условий фильтрации.
Алгоритм действий:
- Создать временную таблицу с нужной структурой колонок.
- Заполнить её данными из таблицы значений 1С.
- Использовать временную таблицу в основном запросе через
ВЫБРАТЬилиГДЕ. - Удалить временную таблицу после выполнения (опционально, так как многие СУБД очищают их автоматически).
Пример кода для Microsoft SQL Server:
// Создаем временную таблицу
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Товар КАК Товар,
| Количество КАК Количество
|ПОМЕСТИТЬ ВТ_Товары
|ИЗ
| &ТаблицаТоваров КАК ТаблицаТоваров";
Запрос.УстановитьПараметр("ТаблицаТоваров", ТаблицаЗначенийТоваров);
Запрос.Выполнить();
// Используем временную таблицу в основном запросе
ОсновнойЗапрос = Новый Запрос;
ОсновнойЗапрос.Текст =
"ВЫБРАТЬ
| Товары.Наименование,
| ВТ_Товары.Количество
|ИЗ
| Справочник.Товары КАК Товары
| ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТ_Товары КАК ВТ_Товары
| ПО Товары.Ссылка = ВТ_Товары.Товар";
Имена колонок временной таблицы совпадают с полями таблицы значений|
Типы данных колонок совместимы (например, не передавать строку в числовое поле)|
Временная таблица удаляется после использования (если не требуется кеширование)|
Учтена специфика СУБД (например, в PostgreSQL временные таблицы создаются иначе)-->
Преимущества метода:
- 🔹 Высокая производительность при больших объемах данных.
- 🔹 Возможность использования индексов временной таблицы.
- 🔹 Поддержка сложных условий соединения и фильтрации.
Недостатки:
- 🔸 Требует прав на создание временных объектов в БД.
- 🔸 Код становится зависим от типа СУБД (синтаксис временных таблиц различается).
- 🔸 Необходимо контролировать очистку таблиц во избежание утечек памяти.
Если временная таблица используется в нескольких запросах подряд, имеет смысл создать её один раз в начале процедуры и удалить в конце. Это сократит накладные расходы на её пересоздание.
Метод 2: Преобразование таблицы в строковый массив
Для небольших таблиц (до сотни строк) удобно преобразовать данные в массив строк или разделенную строку, а затем использовать их в условии В() или ПОДОБНО. Этот метод проще в реализации, но имеет ограничения по объему данных и может тормозить при большом количестве записей.
Пример преобразования таблицы в массив идентификаторов:
МассивИдентификаторов = Новый Массив;
Для Каждого Строка Из ТаблицаЗначений Цикл
МассивИдентификаторов.Добавить(Строка.Идентификатор);
КонецЦикла;
// Используем в запросе
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| *
|ИЗ
| Документ.ЗаказыКлиентов
|ГДЕ
| Ссылка В (&СписокИдентификаторов)";
Запрос.УстановитьПараметр("СписокИдентификаторов", МассивИдентификаторов);
Результат = Запрос.Выполнить();
Когда этот метод оправдан:
- 🔹 Таблица содержит менее 100–200 строк.
- 🔹 Нужно отфильтровать данные по простому условию (например,
Ссылка В (...)). - 🔹 Нет возможности создавать временные таблицы (ограничения прав).
⚠️ Внимание: При использовании массива в условии В() Microsoft SQL Server может ограничивать количество элементов (обычно до 2100). Для больших массивов разбейте их на части или используйте временные таблицы.
Альтернативный вариант — преобразование таблицы в JSON-строку и её разбор на стороне СУБД (если поддерживается, например, в PostgreSQL или SQL Server 2016+). Пример:
JSONСтрока = ЗаписатьJSON(ТаблицаЗначений);
Запрос.Текст = "ВЫБРАТЬ ИЗ Документ.ЗаказыКлиентов ГДЕ Ссылка В (ВЫБРАТЬ Значение ИЗ JSON_QUERY(&JSON, '$.Строки[].Идентификатор'))";
Метод 3: Использование глобальных переменных или общих модулей
В некоторых случаях удобно передавать таблицу значений не через параметры запроса, а через глобальные переменные или экспортные методы общих модулей. Этот подход подходит для внутренних процедур, где данные уже загружены в память и не требуется их сериализация.
Пример с общим модулем:
// В общем модуле "РаботаСДанными" (сервер)
Перем ТаблицаДляЗапроса Экспорт;
// В процедуре заполнения
ТаблицаДляЗапроса = НоваяТаблицаЗначений("Идентификатор, Количество");
ТаблицаДляЗапроса.Добавить(123, 10);
ТаблицаДляЗапроса.Добавить(456, 5);
// В запросе (в том же сеансе)
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Товары.Наименование,
| ?(ВЫБРАТЬ РАЗРЕШЕННЫЕ ТД.Количество
| ИЗ &ТаблицаДанных КАК ТД
| ГДЕ ТД.Идентификатор = Товары.Ссылка) КАК Количество
|ИЗ
| Справочник.Товары КАК Товары";
Запрос.УстановитьПараметр("ТаблицаДанных", РаботаСДанными.ТаблицаДляЗапроса);
Плюсы метода:
- 🔹 Нет необходимости в сериализации данных.
- 🔹 Удобно для многократного использования одной таблицы в разных запросах.
Минусы:
- 🔸 Работает только в рамках одного сеанса.
- 🔸 Может приводить к конфликтам при параллельной работе пользователей.
- 🔸 Требует аккуратного управления памятью (очистка переменных).
⚠️ Внимание: При использовании глобальных переменных в клиент-серверном режиме убедитесь, что данные передаются на сервер через параметры вызова серверных процедур. Иначе таблица останется только на клиенте, и запрос вернёт пустой результат.
Сравнение методов: какой выбрать?
Выбор метода зависит от конкретной задачи. Ниже представлена сравнительная таблица ключевых характеристик:
| Критерий | Временные таблицы | Строковые массивы | Глобальные переменные |
|---|---|---|---|
| Объем данных | Любой (тысячи строк) | До 200–1000 строк | Любой |
| Производительность | ⭐⭐⭐⭐⭐ | ⭐⭐ (замедление при большом количестве) | ⭐⭐⭐⭐ |
| Сложность реализации | Средняя (зависит от СУБД) | Низкая | Низкая |
| Поддержка сложных условий | Да (соединения, группировки) | Ограничено (только простые фильтры) | Да |
| Зависимость от СУБД | Да (синтаксис временных таблиц) | Нет | Нет |
Критическое замечание: При работе с PostgreSQL временные таблицы создаются в отдельной схеме и требуют явного указания имени схемы (например, pg_temp.имя_таблицы). В SQL Server временные таблицы автоматически очищаются по завершении сеанса, а в Oracle их нужно удалять вручную.
Для максимальной производительности при работе с большими данными (10 000+ строк) используйте временные таблицы с индексами. Для простых фильтров по небольшим спискам (до 100 значений) достаточно строковых массивов.
Типичные ошибки и как их избежать
Даже опытные разработчики сталкиваются с проблемами при передаче таблиц в запросы. Рассмотрим наиболее распространённые ошибки и способы их решения:
- 🚨 Ошибка типов данных: Попытка передать строку в числовое поле временной таблицы. Всегда проверяйте соответствие типов колонок таблицы значений и временной таблицы.
- 🚨 Превышение лимитов: В SQL Server условие
INограничено 2100 элементами. Для больших массивов используйте временные таблицы или разбивайте запрос на части. - 🚨 Утечки памяти: Забытые временные таблицы в Oracle или PostgreSQL могут накапливаться и замедлять работу БД. Всегда очищайте их после использования.
- 🚨 SQL-инъекции: При динамическом формировании запроса на основе данных таблицы (например, через
СтрЗаменить()) возможны инъекции. Используйте параметризацию или экранирование.
Пример безопасного формирования строкового массива:
// Небезопасно (риск SQL-инъекции):
Запрос.Текст = "ВЫБРАТЬ * ИЗ Документы ГДЕ Номер В (" + СтрокаИдентификаторов + ")";
// Безопасно (через параметр):
МассивИдентификаторов = Новый Массив;
Для Каждого Строка Из ТаблицаЗначений Цикл
МассивИдентификаторов.Добавить(Число(Строка.Идентификатор)); // Явное приведение типа
КонецЦикла;
Запрос.УстановитьПараметр("СписокИдентификаторов", МассивИдентификаторов);
⚠️ Внимание: В 1С:Предприятие 8.3.20+ появилась поддержка JSON-функций в запросах для некоторых СУБД (например, PostgreSQL). Это позволяет передавать таблицу в виде JSON и разбирать её прямо в SQL, но требует актуальной версии платформы и соответствующей СУБД.
Практические примеры для разных задач
Рассмотрим, как каждый из методов применяется на практике в типовых сценариях работы с 1С.
Пример 1: Фильтрация документов по списку контрагентов
Задача: Получить все заказы клиентов для списка контрагентов, переданного в таблице значений.
Решение: Используем временную таблицу, так как список может быть большим и требуется соединение с основной таблицей.
// 1. Создаем временную таблицу
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Контрагент КАК Контрагент
|ПОМЕСТИТЬ ВТ_Контрагенты
|ИЗ
| &ТаблицаКонтрагентов КАК ТаблицаКонтрагентов";
Запрос.УстановитьПараметр("ТаблицаКонтрагентов", ТаблицаКонтрагентов);
Запрос.Выполнить();
// 2. Основной запрос с соединением
ОсновнойЗапрос = Новый Запрос;
ОсновнойЗапрос.Текст =
"ВЫБРАТЬ
| Заказы.Номер,
| Заказы.Дата,
| Заказы.Сумма
|ИЗ
| Документ.ЗаказыКлиентов КАК Заказы
| ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТ_Контрагенты КАК ВТ_Контрагенты
| ПО Заказы.Контрагент = ВТ_Контрагенты.Контрагент";
Пример 2: Массовое обновление цен номенклатуры
Задача: Обновить цены в справочнике номенклатуры на основе данных из таблицы значений.
Решение: Используем строковый массив для простого условия В(), так как обновление затрагивает небольшое количество позиций.
МассивКодов = Новый Массив;
Для Каждого Строка Из ТаблицаЦен Цикл
МассивКодов.Добавить(Строка.КодНоменклатуры);
КонецЦикла;
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Номенклатура.Ссылка КАК Ссылка,
| ?(ВЫБРАТЬ РАЗРЕШЕННЫЕ ТЦ.Цена
| ИЗ &ТаблицаЦен КАК ТЦ
| ГДЕ ТЦ.КодНоменклатуры = Номенклатура.Код) КАК НоваяЦена
|ИЗ
| Справочник.Номенклатура КАК Номенклатура
|ГДЕ
| Номенклатура.Код В (&СписокКодов)";
Запрос.УстановитьПараметр("ТаблицаЦен", ТаблицаЦен);
Запрос.УстановитьПараметр("СписокКодов", МассивКодов);
Как оптимизировать массовое обновление?
Для ускорения массового обновления используйте пакетные операции:
1. Выгрузите данные в временную таблицу.
2. Выполните одно обновление через запрос с JOIN к временной таблице.
3. Используйте транзакции для отката в случае ошибки.
Пример:
НачатьТранзакцию();
Запрос = Новый Запрос;
Запрос.Текст = "ОБНОВИТЬ Справочник.Номенклатура КАК Номенклатура
|УСТАНОВИТЬ Номенклатура.Цена = ВТ_Цены.НоваяЦена
|ИЗ ВТ_Цены КАК ВТ_Цены
|ГДЕ Номенклатура.Ссылка = ВТ_Цены.Ссылка";
Запрос.Выполнить();
ЗафиксироватьТранзакцию();
FAQ: Частые вопросы по передаче таблиц в запросы
Можно ли передать таблицу значений в запрос без временных таблиц?
Да, но с ограничениями. Для небольших таблиц (до 200 строк) подойдёт преобразование в массив и использование в условии В(). Для больших объемов или сложных условий временные таблицы остаются единственным эффективным решением.
Как передать таблицу в запрос на управляемых формах?
На управляемых формах таблицу значений можно передать через параметры серверной функции, которая выполнит запрос. Пример:
&НаСервере
Функция ПолучитьДанные(ТаблицаПараметров)
Запрос = Новый Запрос;
// Логика работы с таблицей
Возврат Запрос.Выполнить().Выгрузить();
КонецФункции
Вызывайте эту функцию из клиентского кода, передавая таблицу как параметр.
Почему запрос с временной таблицей работает медленно?
Частые причины:
- 🔸 Отсутствуют индексы на колонках временной таблицы, используемых в условиях соединения.
- 🔸 Таблица создаётся заново для каждого запроса (лучше создать один раз и переиспользовать).
- 🔸 СУБД не оптимизирует запросы с временными таблицами (проверьте план выполнения).
Для ускорения добавьте индексы явно:
Запрос.Текст = "ВЫБРАТЬ ... ПОМЕСТИТЬ ВТ_Данные ИНДЕКСИРОВАТЬ ПО Колонка1, Колонка2";
Как передать таблицу в запрос для мобильной платформы 1С?
В мобильной платформе временные таблицы не поддерживаются. Используйте:
- 📱 Преобразование таблицы в массив и условие
В()(для небольших данных). - 📱 Передачу данных через параметры HTTP-сервиса, если запрос выполняется на сервере.
Пример для массива:
МассивДанных = ТаблицаЗначений.ВыгрузитьКолонку("Идентификатор");
Запрос.УстановитьПараметр("Данные", МассивДанных);
Можно ли использовать этот подход в отчетах (СКД)?
В Системе компоновки данных (СКД) передача таблиц в запрос возможна через:
- 📊 Временные таблицы (настраиваются в параметрах схемы компоновки).
- 📊 Параметры отчета, преобразуемые в массивы (для простых фильтров).
Пример настройки временной таблицы в СКД:
// В модуле отчета
Процедура ПриКомпоновкеРезультата(ДанныеРасшифровки, СтандартнаяОбработка)
Если НЕ СтандартнаяОбработка Тогда
СоздатьВременнуюТаблицуДляСКД(ДанныеРасшифровки.Параметры.ТаблицаДанных);
КонецЕсли;
КонецПроцедуры
В схеме компоновки укажите источник данных как запрос с ссылкой на временную таблицу.