Почему зависает запрос к регистру бухгалтерии за короткий период и как оптимизировать работу с таблицей ОстаткиИОбороты?

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

Рассмотрим весьма распространенную ситуацию при разработке отчетов в 1С. Допустим, мы формируем отчет за день по хозрасчетному регистру, используя виртуальную таблицу ОстаткиИОбороты. Неожиданно выясняется, что если выбрать период отчета в 1-2 дня, запрос "зависает" на долгое время, но если выбрать период в целый месяц — отчет формируется мгновенно. Проанализируем причину такого поведения платформы и разберем по шагам, как правильно оптимизировать подобные запросы.

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

Давайте посмотрим на исходный пример запроса, который вызывает проблемы с производительностью:


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

На первый взгляд, все логично: мы берем остатки и обороты, получаем реквизиты регистратора и отсекаем нужные нам склады в секции ГДЕ. Но именно здесь кроется сразу несколько критических ошибок, которые заставляют базу данных выполнять колоссальный объем лишней работы.

Почему запрос за 1-2 дня работает медленнее, чем за месяц?

Выясним причину парадокса со временем выполнения запроса. Виртуальная таблица ОстаткиИОбороты физически не хранится в базе данных. Для её построения платформа 1С использует комбинацию данных из таблицы рассчитанных итогов (которая хранит остатки на границах месяцев) и физической таблицы движений самого регистра.

Если мы запрашиваем данные за полный месяц (от начала до конца), платформа просто берет готовые рассчитанные цифры из таблицы итогов. Это происходит практически мгновенно. Но если мы задаем произвольный период в 1-2 дня внутри месяца, платформе приходится выполнять сложные вычисления. Она берет ближайшие итоги на начало месяца и последовательно прибавляет (или вычитает) все движения (обороты) по таблице регистра до указанных нами дат.

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

Секция ГДЕ против параметров виртуальной таблицы: в чем разница?

Рассмотрим разницу в механике работы отборов. Использование конструкции отбора через реквизиты регистратора в секции ГДЕ крайне неэффективно по архитектурным причинам.

  1. Избыточная выборка: Платформа сначала получает абсолютно все записи из регистра без какого-либо отбора по складам, пересчитывая итоги по миллионам записей.
  2. Неявные соединения: Затем платформа выполняет неявное левое соединение (разыменование ссылки) с физической таблицей документов ПеремещениеТоваров, чтобы получить реквизиты каждого документа-регистратора из выборки.
  3. Поздняя фильтрация: И только после выполнения всех этих тяжелейших операций применяется условие ГДЕ, которое отсеивает 99% полученных данных, оставляя лишь нужные склады.

Перенос условия в параметры виртуальной таблицы заставляет СУБД применять индекс на самом первом этапе выборки. В нашем случае, если склад является одним из субконто (например, Субконто2), код отбора должен быть перенесен внутрь скобок виртуальной таблицы:


ИЗ 
    РегистрБухгалтерии.Хозрасчетный.ОстаткиИОбороты(
        &НачалоПериода, 
        &КонецПериода, 
        Регистратор, , 
        Счет В ИЕРАРХИИ (&Счет), , 
        Субконто2 = &СкладПолучатель // Правильный отбор внутри параметров!
    ) КАК ХозрасчетныйОстаткиИОбороты

Влияние расположения отборов на логику (корректность остатков)

Проанализируем математический аспект: отборы внутри параметров ВТ и в секции ГДЕ дают совершенно разный результат в части остатков.

Отбор внутри параметров ВТ: Платформа изначально рассчитывает начальные и конечные остатки только по выбранному складу. Это логически правильно.

Отбор в ГДЕ: Платформа рассчитает начальные и конечные остатки по счету в целом (по всем складам и субконто), а затем просто скроет из финального результата те строки, где склад регистратора не совпадает с нужным. В итоге в колонке конечного остатка вы получите "общую температуру по больнице", а не остаток по складу.

Как отфильтровать выборку по реквизитам Регистратора?

В параметрах виртуальной таблицы ОстаткиИОбороты нельзя напрямую обратиться к реквизитам документа-регистратора (например, к СкладОтправитель), так как в параметрах доступны только измерения, ресурсы и субконто самого регистра бухгалтерии.

Если задача жестко требует отбора именно по реквизиту регистратора, оптимальнее всего использовать подход с применением временных таблиц. Разберем этот метод по шагам:

  1. Сначала мы пишем запрос к таблице документов ПеремещениеТоваров. Устанавливаем жесткие отборы по складам (Отправитель и Получатель), собираем ссылки на подходящие документы и помещаем их во Временную Таблицу.
  2. Выполняем второй запрос к регистру ОстаткиИОбороты, где с помощью внутреннего соединения (или через конструкцию В) ограничиваем выборку по регистраторам из нашей Временной Таблицы.

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


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

// Шаг 2: Получаем остатки и обороты только по нужным документам
ВЫБРАТЬ
    ХозрасчетныйОстаткиИОбороты.Регистратор КАК Регистратор,
    ВТ_Документы.СкладОтправитель КАК СкладОтправитель,
    ВТ_Документы.СкладПолучатель КАК СкладПолучатель,
    ХозрасчетныйОстаткиИОбороты.Субконто1 КАК Номенклатура,
    ХозрасчетныйОстаткиИОбороты.КоличествоОборот КАК Оборот,
    ХозрасчетныйОстаткиИОбороты.КоличествоКонечныйОстаток КАК КонечныйОстаток
ИЗ
    РегистрБухгалтерии.Хозрасчетный.ОстаткиИОбороты(
        &НачалоПериода, 
        &КонецПериода, 
        Регистратор, , 
        Счет В ИЕРАРХИИ (&Счет), , 
        // Если склад есть в субконто, фильтруем и здесь:
        Субконто2 = &СкладПолучатель 
    ) КАК ХозрасчетныйОстаткиИОбороты
ВНУТРЕННЕЕ СОЕДИНЕНИЕ 
    ВТ_НужныеДокументы КАК ВТ_Документы
ПО 
    ХозрасчетныйОстаткиИОбороты.Регистратор = ВТ_Документы.Регистратор

Подведем итог: При работе с виртуальными таблицами регистров всегда старайтесь максимально ограничивать объем данных на уровне параметров самой виртуальной таблицы. Избегайте разыменования ссылочных полей (получения данных через точку) и фильтрации по ним в секции ГДЕ, особенно если это касается измерений с большой вариативностью. Используйте временные таблицы для предварительного отбора регистраторов — это гарантия быстродействия вашей системы.

← На главную