Часто при разработке отчетов или обработок в 1С мы сталкиваемся с необходимостью отобрать данные (например, остатки или продажи) по определенному списку номенклатуры, контрагентов или документов. Самый очевидный способ — передать список значений в параметр запроса и использовать условие В (&Список). Однако, если этот список содержит сотни или тысячи элементов, производительность запроса катастрофически падает, вплоть до зависания системы.
В этой статье мы подробно разберем, почему возникает эта проблема, и рассмотрим профессиональное решение: использование объекта МенеджерВременныхТаблиц. Мы проанализируем код, научимся передавать таблицы значений в запрос и правильно индексировать данные для максимальной скорости.
Давайте разберемся в механике процесса. Когда мы передаем массив или список значений в параметры запроса 1С, платформа преобразует их в SQL-конструкцию WHERE Field IN (Val1, Val2, ..., ValN). СУБД (будь то MS SQL или PostgreSQL) имеет ограничения на сложность таких конструкций.
Проанализируем основные недостатки этого метода при больших объемах данных:
Table Scan (полному перебору таблицы) вместо использования индексов.Посмотрим на решение, которое считается стандартом в оптимизации 1С — помещение списка отбора во временную таблицу.
Для того чтобы использовать временные таблицы, нам необходимо сначала подготовить объект МенеджерВременныхТаблиц. Этот объект служит контейнером, в котором временные таблицы будут жить в течение времени жизни нашего запроса или пакета запросов.
Рассмотрим последовательность действий. Сначала нам нужно создать сам запрос и привязать к нему новый менеджер. Предположим, у нас есть массив ссылок на номенклатуру МассивНоменклатуры, по которому нужно получить остатки.
// Создаем запрос
Запрос = Новый Запрос;
// Создаем и присваиваем менеджер временных таблиц
Запрос.МенеджерВременныхТаблиц = Новый МенеджерВременныхТаблиц;
Временную таблицу можно создать непосредственно внутри запроса, выбрав данные из базы, но в нашем случае данные приходят "снаружи" (из кода). Чтобы поместить внешний список во временную таблицу, нам нужно передать его как параметр. Но простой массив не подойдет — нам нужна ТаблицаЗначений с типизированными колонками.
Разберем этот этап подробно. Сформируем таблицу значений и поместим её в запрос:
// 1. Преобразуем массив в Таблицу Значений (если у нас еще нет ТЗ)
ТЗ_Отбор = Новый ТаблицаЗначений;
ТЗ_Отбор.Колонки.Добавить("Номенклатура", Новый ОписаниеТипов("СправочникСсылка.Номенклатура"));
Для Каждого ЭлементИзМассива Из МассивНоменклатуры Цикл
НовСтр = ТЗ_Отбор.Добавить();
НовСтр.Номенклатура = ЭлементИзМассива;
КонецЦикла;
// 2. Передаем таблицу как параметр в запрос
Запрос.Текст =
"ВЫБРАТЬ
| ТЗ.Номенклатура КАК Номенклатура
|ПОМЕСТИТЬ ВТ_ФильтрНоменклатуры
|ИЗ
| &ВнешняяТЗ КАК ТЗ
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| ВТ_ФильтрНоменклатуры.Номенклатура
|ИЗ
| ВТ_ФильтрНоменклатуры КАК ВТ_ФильтрНоменклатуры";
// Важно: параметр называется так же, как в тексте запроса
Запрос.УстановитьПараметр("ВнешняяТЗ", ТЗ_Отбор);
// Выполняем первую часть (помещение данных)
Запрос.Выполнить();
Обратите внимание на конструкцию ИЗ &ВнешняяТЗ КАК ТЗ. Это специальный синтаксис 1С для работы с внешними источниками данных внутри запроса. Платформа берет нашу таблицу ТЗ_Отбор, создает на сервере СУБД временную таблицу ВТ_ФильтрНоменклатуры и загружает туда данные. Теперь мы можем обращаться к ним средствами SQL.
Просто поместить данные во временную таблицу недостаточно. Если мы будем соединять нашу временную таблицу с реальными таблицами базы данных (например, регистром накопления ТоварыНаСкладах), СУБД должна быстро искать соответствия. Без индекса соединение может работать медленно.
Добавим индекс по полю Номенклатура при создании временной таблицы. Посмотрим, как изменится текст запроса:
Запрос.Текст =
"ВЫБРАТЬ
| ТЗ.Номенклатура КАК Номенклатура
|ПОМЕСТИТЬ ВТ_ФильтрНоменклатуры
|ИЗ
| &ВнешняяТЗ КАК ТЗ
|
|ИНДЕКСИРОВАТЬ ПО
| Номенклатура";
Теперь СУБД создаст физический индекс для временной таблицы, что значительно ускорит последующее соединение (JOIN).
Теперь, когда наша временная таблица ВТ_ФильтрНоменклатуры находится в менеджере и проиндексирована, мы можем написать основной запрос для получения остатков. Мы используем ВНУТРЕННЕЕ СОЕДИНЕНИЕ (INNER JOIN), чтобы отсечь все лишние записи регистра и оставить только те, которые есть в нашем списке.
Рассмотрим полный код решения задачи:
Запрос = Новый Запрос;
Запрос.МенеджерВременныхТаблиц = Новый МенеджерВременныхТаблиц;
// Шаг 1: Помещаем список во временную таблицу
Запрос.Текст =
"ВЫБРАТЬ
| ТЗ.Номенклатура КАК Номенклатура
|ПОМЕСТИТЬ ВТ_Фильтр
|ИЗ
| &ТЗ_Данные КАК ТЗ
|
|ИНДЕКСИРОВАТЬ ПО
| Номенклатура";
Запрос.УстановитьПараметр("ТЗ_Данные", ТЗ_Отбор);
Запрос.Выполнить(); // В этот момент создается ВТ
// Шаг 2: Используем ВТ для фильтрации основной таблицы
Запрос.Текст =
"ВЫБРАТЬ
| ТоварыНаСкладахОстатки.Склад КАК Склад,
| ТоварыНаСкладахОстатки.Номенклатура КАК Номенклатура,
| ТоварыНаСкладахОстатки.КоличествоОстаток КАК Количество
|ИЗ
| ВТ_Фильтр КАК ВТ_Фильтр
| ВНУТРЕННЕЕ СОЕДИНЕНИЕ РегистрНакопления.ТоварыНаСкладах.Остатки(
| ,
| Номенклатура В
| (ВЫБРАТЬ
| Т.Номенклатура
| ИЗ
| ВТ_Фильтр КАК Т)) КАК ТоварыНаСкладахОстатки
| ПО ВТ_Фильтр.Номенклатура = ТоварыНаСкладахОстатки.Номенклатура";
// Вариант соединения может отличаться в зависимости от задачи (Левое или Внутреннее)
// Но суть остается прежней: мы ограничиваем выборку данными из ВТ_Фильтр
Результат = Запрос.Выполнить();
Проанализируем несколько технических тонкостей, которые помогут избежать ошибок:
ТаблицаЗначений программно, обязательно указывайте типы колонок через ОписаниеТипов. Если тип не указан или он составной (и не уточнен), запрос может выдать ошибку или работать некорректно при создании временной таблицы.МенеджерВременныхТаблиц, хорошим тоном при написании сложных пакетных запросов считается явное удаление таблицы командой УНИЧТОЖИТЬ ВТ_ИмяТаблицы, если она больше не нужна в дальнейших шагах пакета. Это освобождает память tempdb на SQL сервере.Использование объекта МенеджерВременныхТаблиц для фильтрации данных по большим спискам — это фундаментальный навык для программиста 1С. Этот подход снижает нагрузку на СУБД, уменьшает время блокировок таблиц и делает выполнение отчетов предсказуемым по времени. Всегда используйте этот метод вместо передачи огромных массивов напрямую в параметры &Список, если количество элементов превышает несколько сотен.