В процессе разработки на платформе 1С:Предприятие 8.3 программисты часто сталкиваются с ситуацией, когда запрос, идеально работающий на тестовой базе, начинает катастрофически замедляться на реальных данных. Одной из главных причин такого поведения является использование сложных вложенных запросов в условии ГДЕ или соединений с виртуальными таблицами. Давайте проанализируем, почему это происходит, и разберем по шагам, как перевести такие конструкции на использование временных таблиц.
Когда мы используем конструкцию типа ГДЕ Ссылка В (ВЫБРАТЬ...), оптимизатор системы управления базами данных (СУБД), будь то MS SQL Server или PostgreSQL, не всегда может эффективно определить объем данных, который вернет вложенный запрос. В результате выбирается неоптимальный план выполнения, что приводит к полному сканированию таблиц (Index Scan или Table Scan) вместо быстрого поиска по индексу. Чтобы избежать этого, мы воспользуемся механизмом, который предоставляет нам МенеджерВременныхТаблиц.
Прежде чем приступать к написанию самого текста запроса, нам необходимо инициализировать объект, который будет хранить наши промежуточные данные в оперативной памяти или в специальных системных таблицах СУБД (tempdb). Рассмотрим пример инициализации в коде 1С:
МенеджерВТ = Новый МенеджерВременныхТаблиц;
Запрос = Новый Запрос;
Запрос.МенеджерВременныхТаблиц = МенеджерВТ;
Использование МенеджерВременныхТаблиц позволяет нам связывать несколько последовательных запросов в одну логическую цепочку, где результаты предыдущего шага доступны для последующих. Это критически важно для структурирования сложной логики.
Представим, что у нас есть задача: получить остатки только по тем товарам, которые продавались в прошлом месяце. Вместо того чтобы делать соединение с виртуальной таблицей оборотов внутри основного запроса, мы сначала выберем все проданные товары во временную таблицу. Разберем этот пример подробнее.
Проанализируем текст первого запроса, который подготавливает данные:
Запрос.Текст =
"ВЫБРАТЬ РАЗЛИЧНЫЕ
| ПродажиОбороты.Номенклатура КАК Номенклатура
|ПОМЕСТИТЬ ВТ_ПроданныеТовары
|ИЗ
| РегистрНакопления.Продажи.Обороты(&НачалоПериода, &КонецПериода) КАК ПродажиОбороты
|ИНДЕКСИРОВАТЬ ПО
| Номенклатура";
Запрос.Выполнить();
Обратите внимание на ключевое слово ПОМЕСТИТЬ. Оно указывает платформе 1С, что результат выполнения не нужно возвращать в код, а следует сохранить в таблицу с именем ВТ_ПроданныеТовары. Также крайне важным моментом является использование предложения ИНДЕКСИРОВАТЬ ПО. Индексация временной таблицы позволяет СУБД мгновенно находить соответствия при последующем соединении с основной таблицей остатков.
Теперь, когда наши данные подготовлены и проиндексированы, мы можем выполнить основной запрос. Посмотрим на пример кода, где мы соединяем остатки с нашей временной таблицей:
Запрос.Текст =
"ВЫБРАТЬ
| ТоварыНаСкладахОстатки.Номенклатура,
| ТоварыНаСкладахОстатки.КоличествоОстаток
|ИЗ
| РегистрНакопления.ТоварыНаСкладах.Остатки(&Период, ) КАК ТоварыНаСкладахОстатки
|ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТ_ПроданныеТовары КАК ВТ_ПроданныеТовары
|ПО ТоварыНаСкладахОстатки.Номенклатура = ВТ_ПроданныеТовары.Номенклатура";
РезультатЗапроса = Запрос.Выполнить();
В данном случае ВНУТРЕННЕЕ СОЕДИНЕНИЕ с предварительно отобранными данными работает значительно быстрее, чем использование условий во вложенных подзапросах. Мы фактически ограничили выборку остатков только теми позициями, которые гарантированно есть в нашей ВТ_ПроданныеТовары.
Рассмотрим несколько правил, которые помогут вам писать эффективный код:
СОЕДИНЕНИЕ или фильтрация в секции ГДЕ. Без индекса временная таблица — это просто неструктурированный набор данных.ВЫБРАТЬ... ПОМЕСТИТЬ, старайтесь избегать полей с составным типом данных. Это может негативно сказаться на скорости индексации.Посмотрим, как правильно очистить менеджер временных таблиц:
Запрос.Текст = "УНИЧТОЖИТЬ ВТ_ПроданныеТовары";
Запрос.Выполнить();
Проанализируем ситуацию на конкретном примере. Допустим, нам нужно получить данные из справочника Номенклатура, исключая те элементы, которые помечены на удаление или входят в определенный сегмент.
Плохо (Вложенный запрос):
ВЫБРАТЬ Ссылка ИЗ Справочник.Номенклатура
ГДЕ Ссылка В (ВЫБРАТЬ Номенклатура ИЗ РегистрСведений.НоменклатураСегмента ГДЕ Сегмент = &Сегмент)
Хорошо (Временная таблица):
ВТ_Сегмент.ВТ_Сегмент по полю Номенклатура.Справочник.Номенклатура с ВТ_Сегмент через ВНУТРЕННЕЕ СОЕДИНЕНИЕ.Такой подход гарантирует, что СУБД построит план запроса на основе Nested Loops или Hash Match, используя существующие индексы основной таблицы и созданный нами индекс временной таблицы. Это исключает непредсказуемое поведение оптимизатора.
Разберем ситуации, в которых без этого механизма практически не обойтись:
Остатки и Цены.СрезПоследних). Соединение виртуальных таблиц напрямую — одна из самых частых причин "зависания" отчетов.В заключение отметим, что переход на временные таблицы требует от разработчика дисциплины и понимания того, как работают индексы. Однако результат в виде стабильной и быстрой работы программы оправдывает все затраченные усилия. Мы рекомендуем всегда проверять тяжелые запросы в консоли запросов с включенным замером времени, чтобы наглядно видеть преимущество использования временных таблиц в вашей конкретной конфигурации.