Как правильно работать с составными типами в запросах 1С для обеспечения максимальной производительности

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

Работа с полями составного типа в запросах 1С — это классическая «ловушка» для разработчиков, особенно при обращении к таким реквизитам, как Регистратор, Владелец или ДокументОснование. Если не уделять внимания типизации, запрос может выполняться недопустимо долго, а в некоторых случаях даже приводить к аварийному завершению сеанса SQL-сервера. В данной статье мы подробно разберем, почему «обращение через точку» к составному полю замедляет систему, и как переписать запрос так, чтобы он работал максимально эффективно.

Почему обращение через точку к составному типу — это плохо?

Разберем внутренний механизм работы платформы. Когда мы пишем в запросе конструкцию вида Регистратор.Дата, и поле Регистратор имеет составной тип (например, может содержать 50 различных видов документов), платформа 1С при генерации итогового SQL-запроса вынуждена выполнить левое соединение (LEFT OUTER JOIN) со всеми 50 таблицами этих документов. Это явление часто называют «взрывом» запроса.

Проанализируем последствия такого «взрыва»:

  1. Огромный текст SQL-запроса: Вместо лаконичного кода сервер БД получает простыню из сотен строк.
  2. Нагрузка на оптимизатор SQL: СУБД тратит значительное время (иногда секунды!) только на то, чтобы построить план выполнения такого сложного запроса.
  3. Неоптимальный план: Из-за обилия соединений оптимизатор может ошибиться и выбрать неэффективный индекс, что приведет к полному сканированию огромных таблиц (Table Scan).

Использование оператора ВЫРАЗИТЬ для ограничения типов

Самый простой и стандартный способ оптимизации — использование функции ВЫРАЗИТЬ. Она сообщает платформе, что в данной точке запроса нас интересует только один конкретный тип из всех возможных. Это позволяет платформе исключить лишние LEFT JOIN к другим таблицам.

Рассмотрим пример правильной типизации во временных таблицах. Важно помнить: если мы просто отфильтровали данные по типу через ССЫЛКА, но не применили ВЫРАЗИТЬ при выборе поля, платформа всё равно может оставить лишние соединения в последующих запросах пакета.


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

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    // Обязательно типизируем поле при обращении к реквизитам
    ВЫРАЗИТЬ(ВТ_ДанныеРегистра.Регистратор КАК Документ.СчетФактураВыданный).ВидСчетаФактуры КАК ВидСФ,
    ВТ_ДанныеРегистра.Сумма
ИЗ
    ВТ_ДанныеРегистра КАК ВТ_ДанныеРегистра

Важный нюанс: Как отмечают эксперты, даже фильтрация через ССЫЛКА не является для платформы 1С указанием на то, что поле теперь имеет только один тип. Явным ограничением типа для генератора SQL-запросов является только оператор ВЫРАЗИТЬ в списке выбираемых полей или условиях.

Разбор «слоеного пирога» из временных таблиц

В обсуждении часто всплывает вопрос: стоит ли дробить запрос на множество временных таблиц (ВТ)? Рассмотрим стратегию «мега-предусмотрительного» подхода, когда на каждом этапе мы фильтруем и типизируем данные.

Проанализируем ситуацию, когда нам нужно достать данные из «второго уровня» вложенности: Регистратор.ДокументОснование.ДоговорКонтрагента. Здесь у нас сразу два составных поля. Оптимальная схема действий выглядит так:

  1. Выбираем данные из регистра во временную таблицу, фильтруя по Регистратор ССЫЛКА Документ.СчетФактураВыданный.
  2. В следующей ВТ мы уже обращаемся к Регистратору через ВЫРАЗИТЬ, чтобы получить ДокументОснование.
  3. Там же мы ограничиваем тип ДокументОснование (например, КАК Документ.ПоступлениеНаРасчетныйСчет).
  4. В финальной выборке мы уже безопасно получаем ДоговорКонтрагента.

Пример реализации такого подхода:


ВЫБРАТЬ
    ВЫРАЗИТЬ(Рег.Регистратор КАК Документ.СчетФактураВыданный) КАК СсылкаСФ,
    Рег.СуммаОборот КАК Сумма
ПОМЕСТИТЬ ВТ_СФ
ИЗ
    РегистрБухгалтерии.Хозрасчетный.ОборотыДтКт(..., Регистратор ССЫЛКА Документ.СчетФактураВыданный, ...) КАК Рег;

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

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    ВТ_Основания.СсылкаСФ,
    ВТ_Основания.Основание.ДоговорКонтрагента КАК Договор,
    ВТ_Основания.Сумма
ИЗ
    ВТ_Основания КАК ВТ_Основания

Явное соединение: ультимативный метод ускорения

Выясним причину, по которой даже ВЫРАЗИТЬ иногда проигрывает. Несмотря на типизацию, платформа всё равно использует LEFT JOIN. Если мы точно знаем, что нам нужны данные только из определенных документов, явное внутреннее соединение (INNER JOIN) будет работать быстрее всего.

Рассмотрим рекомендации по переходу от виртуальных таблиц к реальным в высоконагруженных системах:

  1. Использование таблицы движений регистра (например, Хозрасчетный.ДвиженияССубконто) вместо виртуальной таблицы Обороты может быть эффективнее, если вы используете стандартные индексы (Период + Счет + ...).
  2. Явное соединение с таблицей Документ.СчетФактураВыданный по полю Ссылка = Регистратор позволит SQL-серверу использовать INNER JOIN и максимально эффективно применить индексы документа (например, по ДокументОснование или ВидСчетаФактуры).

Посмотрим на пример такого «ручного» соединения:


ВЫБРАТЬ
    Движения.Регистратор,
    ТаблицаСФ.ДокументОснование,
    Движения.Сумма
ИЗ
    РегистрБухгалтерии.Хозрасчетный КАК Движения
    ВНУТРЕННЕЕ СОЕДИНЕНИЕ Документ.СчетФактураВыданный КАК ТаблицаСФ
    ПО Движения.Регистратор = ТаблицаСФ.Ссылка
    ВНУТРЕННЕЕ СОЕДИНЕНИЕ Документ.ПоступлениеНаРасчетныйСчет КАК ТаблицаОснование
    ПО ТаблицаСФ.ДокументОснование = ТаблицаОснование.Ссылка
ГДЕ
    Движения.Период МЕЖДУ &НачалоПериода И &КонецПериода
    И ТаблицаСФ.ВидСчетаФактуры = ЗНАЧЕНИЕ(Перечисление.ВидСчетаФактурыВыставленного.НаАванс)
    И Движения.СчетДт = &СчетНДС

Практические советы по работе с индексами

Проанализируем ситуацию с точки зрения структуры базы данных. В типовых конфигурациях (например, БП 3.0) реквизиты документов часто уже индексированы. Например, поле ВидСчетаФактуры и ДокументОснование в СчетФактураВыданный имеют индексы.

Чтобы эти индексы «заиграли» в вашем запросе, старайтесь:

Подведение итогов

Мы рассмотрели три основных подхода к оптимизации. Выясним, какой из них выбрать:

  1. Для простых отчетов: Достаточно использовать ВЫРАЗИТЬ(Регистратор КАК Документ.Тип). Это защитит от «взрыва» запроса и будет вполне читаемо.
  2. Для сложных расчетов и больших объемов: Используйте пакетные запросы с временными таблицами, в которых на каждом шаге происходит фильтрация (ССЫЛКА) и типизация (ВЫРАЗИТЬ).
  3. Для критичных по скорости участков: Откажитесь от виртуальных таблиц и обращения через точку в пользу явных соединений с физическими таблицами документов. Это даст вам полный контроль над планом SQL-запроса.

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

← На главную