Как оптимизировать запрос для проверки наличия движений документа по всем регистрам

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

При разработке технических отчетов или инструментов аудита базы часто возникает задача: выяснить, по каким регистрам конкретный документ (например, РеализацияТоваровУслуг) сформировал записи. Если регистров в системе много (40 и более), стандартный подход с использованием множества ЛЕВОЕ СОЕДИНЕНИЕ начинает работать крайне медленно или вовсе приводит к зависанию системы. В этой статье мы разберем, почему это происходит, и проанализируем эффективные методы оптимизации таких запросов.

Почему исходный запрос работает медленно?

Рассмотрим типовую ошибку. Часто разработчики пытаются выбрать данные из каждого регистра, фильтруя их по дате через регистратор: ГДЕ ВыборкаИзРегистра.Регистратор.Дата МЕЖДУ &Дата1 И &Дата2. Проанализируем ситуацию: поле Регистратор является ссылочным типом. Обращение Регистратор.Дата — это неявное соединение с таблицей документа. Если у нас 44 регистра, СУБД приходится выполнять 44 дополнительных соединения только для того, чтобы проверить дату. В сочетании с 44 левыми соединениями в основном запросе это создает колоссальную нагрузку на оптимизатор SQL-запросов.

Решение 1: Использование проиндексированных временных таблиц

Первый шаг к оптимизации — подготовка списка документов во временной таблице. Это позволит СУБД четко понимать объем данных, который нужно сопоставить с регистрами. Выясним, как правильно перестроить запрос по шагам:

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

Рассмотрим пример правильной структуры запроса:


// Шаг 1: Готовим документы
ВЫБРАТЬ
    Док.Ссылка КАК Ссылка
ПОМЕСТИТЬ ВТ_Док
ИЗ
    Документ.РеализацияТоваровУслуг КАК Док
ГДЕ
    Док.Дата МЕЖДУ &Дата1 И &Дата2
    И Док.Проведен = ИСТИНА
ИНДЕКСИРОВАТЬ ПО
    Ссылка;

////////////////////////////////////////////////////////////////////////////////
// Шаг 2: Получаем данные по регистрам (пример для двух регистров)
ВЫБРАТЬ
    Док.Ссылка КАК Регистратор,
    КОЛИЧЕСТВО(Рег.НомерСтроки) КАК КоличествоЗаписей
ПОМЕСТИТЬ ВТ_Хозрасчетный
ИЗ
    ВТ_Док КАК Док
        ЛЕВОЕ СОЕДИНЕНИЕ РегистрБухгалтерии.Хозрасчетный КАК Рег
        ПО Док.Ссылка = Рег.Регистратор
СГРУППИРОВАТЬ ПО
    Док.Ссылка;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    Док.Ссылка КАК Регистратор,
    КОЛИЧЕСТВО(Рег.НомерСтроки) КАК КоличествоЗаписей
ПОМЕСТИТЬ ВТ_НДСНачисленный
ИЗ
    ВТ_Док КАК Док
        ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.НДСНачисленный КАК Рег
        ПО Док.Ссылка = Рег.Регистратор
СГРУППИРОВАТЬ ПО
    Док.Ссылка;

Важный нюанс: использование ЛЕВОЕ СОЕДИНЕНИЕ внутри временных таблиц для каждого регистра позволяет СУБД выполнять задачи последовательно. Однако, если регистров все еще 44, итоговое объединение этих ВТ может занять время.

Решение 2: Оптимизация через ОБЪЕДИНИТЬ ВСЕ (UNION ALL)

Если нам нужно просто вывести таблицу, где в колонках указано наличие движений, можно применить метод «разворота». Вместо тяжелых соединений мы соберем данные «в столбик» через ОБЪЕДИНИТЬ ВСЕ, а затем свернем их. Этот метод работает значительно быстрее, так как СУБД выполняет линейное чтение данных.

Разберем пример реализации, где для каждого регистра выделяется своя колонка:


ВЫБРАТЬ
    Т.Ссылка,
    СУММА(Т.Рег1) КАК Хозрасчетный,
    СУММА(Т.Рег2) КАК НДСНачисленный
ИЗ (
    // Базовая выборка всех документов
    ВЫБРАТЬ Ссылка, 0 КАК Рег1, 0 КАК Рег2 ИЗ ВТ_Док
    
    ОБЪЕДИНИТЬ ВСЕ
    
    // Данные по первому регистру
    ВЫБРАТЬ Регистратор, 1, 0 
    ИЗ РегистрБухгалтерии.Хозрасчетный 
    ГДЕ Регистратор В (ВЫБРАТЬ Ссылка ИЗ ВТ_Док)
    
    ОБЪЕДИНИТЬ ВСЕ
    
    // Данные по второму регистру
    ВЫБРАТЬ Регистратор, 0, 1 
    ИЗ РегистрНакопления.НДСНачисленный 
    ГДЕ Регистратор В (ВЫБРАТЬ Ссылка ИЗ ВТ_Док)
) КАК Т
СГРУППИРОВАТЬ ПО Т.Ссылка

Этот подход избавляет оптимизатор от необходимости строить сложный план соединений (Join Plan) для 40+ таблиц. Каждая секция ОБЪЕДИНИТЬ ВСЕ — это простой запрос, результат которого просто добавляется в общую выборку.

Рекомендации по дальнейшей оптимизации

Проанализируем дополнительные технические приемы, которые помогут ускорить работу еще сильнее:

  1. Использование поля Период: Во всех регистрах поле Период проиндексировано. Если вы все же используете фильтры внутри запросов к регистрам, всегда используйте Период МЕЖДУ &Дата1 И &Дата2 вместо обращения к дате через регистратор.
  2. Проверка существования вместо подсчета: Если вам не нужно точное количество строк, а достаточно флага «Есть/Нет», используйте ВЫБРАТЬ РАЗЛИЧНЫЕ Регистратор. Это позволит системе быстрее сворачивать данные.
  3. Программное формирование запроса: Чтобы не писать 44 объединения вручную, используйте объектную модель метаданных. Мы можем перебрать коллекцию Движения выбранного документа и динамически собрать текст запроса.
  4. Использование СКД: Если вывести данные нужно вертикально (Документ - Регистр - Наличие), лучше всего использовать Систему Компоновки Данных с одним набором данных типа «Объединение». Это позволит пользователю самому выбирать, какие регистры он хочет видеть в отчете, не перегружая систему лишними выборками.

Резюме: Основная причина тормозов в подобных задачах — избыточные соединения и обращения к реквизитам через точку. Переход к использованию проиндексированных ВТ и замена ЛЕВОЕ СОЕДИНЕНИЕ на ОБЪЕДИНИТЬ ВСЕ позволяет сократить время выполнения отчета с «бесконечности» до нескольких секунд или минут в зависимости от объема данных.

← На главную