Как оптимизировать сложный запрос 1С к виртуальным таблицам и документам разных типов?

Программист 1С v8.3 (Обычные формы) Управленческий учет IT и автоматизация бизнеса
← На главную

При разработке сложных отчетов или выборок данных в 1С программисты часто сталкиваются с проблемой низкой производительности запросов. Ситуация усугубляется, когда в одном запросе требуется объединить данные из нескольких регистров сведений и документов различных типов. В данной статье мы проанализируем типичный пример "тяжелого" запроса, выявим его слабые места и разберем по шагам, как превратить медленный код в эффективное решение.

Анализ проблемных зон исходного запроса

Проанализируем представленный в теме запрос. Основная причина его долгой работы заключается в нескольких критических ошибках проектирования:

  1. Многократный вызов тяжелых виртуальных таблиц: В тексте трижды вызывается СрезПоследних для одного и того же регистра сведений. Каждое такое обращение — это отдельная сложная операция на уровне СУБД.
  2. Использование вложенных запросов: Вложенные запросы крайне сложны для оптимизатора SQL-сервера, так как он не может заранее рассчитать статистику и построить оптимальный план выполнения.
  3. Обращение через точку к полям составного типа: Конструкции вида Заявка.Организация или Заявка.Контрагент при условии, что Заявка — это поле составного типа, порождают огромное количество левых соединений (LEFT JOIN) в итоговом SQL-запросе.
  4. Неэффективные фильтры в параметрах виртуальных таблиц: Использование ВЫРАЗИТЬ и условий по дате документа внутри параметров СрезПоследних заставляет систему сначала выполнять соединения с таблицами документов для каждой записи регистра, и только потом получать срез.

Шаг 1: Подготовка данных во временных таблицах

Рассмотрим правильный подход. Вместо того чтобы пытаться достать всё одним огромным запросом, мы разделим задачу на этапы. Сначала соберем все необходимые ссылки на документы за нужный период во временную таблицу. Это позволит нам один раз обратиться к таблицам документов и получить все нужные реквизиты (дату, номер, организацию).

Рассмотрим пример кода для подготовки списка документов:


// Помещаем во временную таблицу только нужные документы за период
Запрос.Текст = "ВЫБРАТЬ
               |    Док.Ссылка КАК Заявка,
               |    Док.Дата КАК ДатаЗаявки,
               |    Док.Номер КАК НомерДокумента,
               |    Док.Организация КАК Организация,
               |    Док.Контрагент КАК Контрагент,
               |    ТИПЗНАЧЕНИЯ(Док.Ссылка) КАК ТипДокумента
               |ПОМЕСТИТЬ ВТ_СписокДокументов
               |ИЗ
               |    Документ.бит_ПроектДоговора КАК Док
               |ГДЕ
               |    Док.Дата МЕЖДУ &НачалоПериода И &КонецПериода
               |
               |ОБЪЕДИНИТЬ ВСЕ
               |
               |ВЫБРАТЬ
               |    Док.Ссылка, Док.Дата, Док.Номер, Док.Организация, Док.Контрагент, ТИПЗНАЧЕНИЯ(Док.Ссылка)
               |ИЗ
               |    Документ.ЗаявкаНаСогласованиеАктаВыполненныхРабот КАК Док
               |ГДЕ
               |    Док.Дата МЕЖДУ &НачалоПериода И &КонецПериода
               |;
               |
               |////////////////////////////////////////////////////////////////////////////////
               |ИНДЕКСИРОВАТЬ ПО
               |    Заявка";

Важный момент: Мы использовали команду ИНДЕКСИРОВАТЬ ПО. Это критически важно для последующих соединений с этой таблицей. Индекс позволит SQL-серверу мгновенно находить нужные строки при связи с регистрами.

Шаг 2: Оптимизация получения данных из регистра сведений

Теперь выясним причину, почему исходный СрезПоследних работал медленно. Проблема в том, что он пытался обработать весь регистр. Теперь, когда у нас есть ВТ_СписокДокументов, мы можем передать её в качестве фильтра в параметры виртуальной таблицы. Таким образом, система будет искать данные только по тем объектам, которые нас действительно интересуют.

Разберем, как это реализовать:


|ВЫБРАТЬ
|    Статусы.Объект КАК Объект,
|    Статусы.Статус КАК Статус
|ПОМЕСТИТЬ ВТ_Статусы
|ИЗ
|    РегистрСведений.бит_ИсторияИзмененияСтатусовОбъектов.СрезПоследних(&КонецПериода, Объект В (ВЫБРАТЬ Заявка ИЗ ВТ_СписокДокументов)) КАК Статусы
|ИНДЕКСИРОВАТЬ ПО
|    Объект";

Обратите внимание: мы используем конструкцию Объект В (ВЫБРАТЬ Заявка ИЗ ВТ_СписокДокументов) в первом параметре виртуальной таблицы. Это позволяет значительно сократить выборку еще на этапе работы движка 1С, до того как данные попадут в основную выборку.

Шаг 3: Объединение данных и работа с регистрами доступности

Проанализируем ситуацию с регистрами доступности. В исходном запросе автор использовал вложенные запросы к СтоунДоступностьЗаявок и СтоунДоступностьЗаявокВАрхиве. Мы заменим их на плоские соединения временных таблиц. Если данных много, рекомендуется также предварительно отобрать записи из этих регистров во временные таблицы по фильтру Пользователь = &Ответственный.

Посмотрим на пример финального объединения:


|ВЫБРАТЬ
|    Список.ДатаЗаявки,
|    Список.НомерДокумента,
|    Список.Заявка КАК Документ,
|    Список.Организация,
|    Список.Контрагент,
|    Доступность.Пользователь,
|    Статусы.Статус
|ИЗ
|    ВТ_СписокДокументов КАК Список
|    ВНУТРЕННЕЕ СОЕДИНЕНИЕ РегистрСведений.СтоунДоступностьЗаявок КАК Доступность
|        ПО Список.Заявка = Доступность.Заявка
|        И Доступность.Пользователь = &Ответственный
|        И Доступность.Доступ = ИСТИНА
|    ЛЕВОЕ СОЕДИНЕНИЕ ВТ_Статусы КАК Статусы
|        ПО Список.Заявка = Статусы.Объект";

Основные рекомендации по ускорению запросов

Подведем итоги и сформулируем правила, которые помогут избежать медленной работы в будущем:

  1. Откажитесь от «точек» через поля составного типа. Если поле Объект может быть и Договором, и Заявкой, обращение Объект.Дата заставит 1С соединить таблицы всех возможных типов этого объекта. Всегда выбирайте реквизиты из конкретных таблиц документов и помещайте их во временную таблицу.
  2. Используйте ВЫРАЗИТЬ только там, где это необходимо. В условиях связи ПО лучше сравнивать чистые ссылки, а не результаты функции ВЫРАЗИТЬ. Если вы уже знаете тип документа (например, выбрали его из таблицы конкретного документа), дополнительное типизирование только замедлит процесс.
  3. Временные таблицы — наш лучший друг. Временная таблица с индексом всегда быстрее вложенного запроса. Она позволяет SQL-серверу построить точный план выполнения.
  4. Фильтруйте виртуальные таблицы в параметрах. Никогда не ставьте условия на данные виртуальных таблиц в блоке ГДЕ. Всегда используйте первый параметр (условие отбора), чтобы СУБД получила уже отфильтрованный результат.
  5. Минимизируйте использование UNION в основном теле запроса. Старайтесь объединять данные на этапе формирования временных таблиц со ссылками. Финальная выборка должна быть максимально простой и состоять из соединений (JOIN) подготовленных таблиц.

Соблюдение этих правил позволит вам писать запросы, которые работают стабильно быстро даже на базах данных объемом в несколько терабайт.

← На главную