Как ускорить запрос к физическим таблицам регистра бухгалтерии при расчете остатков по документам

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

В практике разработки на платформе 1С встречаются ситуации, когда использование стандартных виртуальных таблиц (Остатки, Обороты, ОстаткиИОбороты) становится невозможным. Чаще всего это происходит из-за повреждения итоговых таблиц базы данных или специфических требований к расчету нарастающих итогов "на каждый документ" в рамках одного запроса. Работа с физическими таблицами напрямую требует от программиста глубокого понимания механизмов индексации СУБД и особенностей компоновки запросов.

Разберем детально, как можно оптимизировать тяжелый запрос к регистру Хозрасчетный, который собирает данные по списанию материальных запасов, используя физические таблицы проводок и субконто.

Анализ проблемы: почему запрос работает медленно?

Проанализируем исходную ситуацию. Основная нагрузка в таких запросах ложится на несколько узких мест:

  1. Обращение к физической таблице проводок с условием ИЛИ: Использование СчетДт В (...) ИЛИ СчетКт В (...) заставляет СУБД выполнять полное сканирование индекса или всей таблицы, так как она не может эффективно применить индекс сразу к двум разным полям.
  2. Соединение с таблицей субконто: Физическая таблица РегистрБухгалтерии.Хозрасчетный.Субконто содержит огромное количество записей (кратное количеству проводок и видов субконто). Соединение с ней по Регистратору — одна из самых дорогих операций.
  3. Расчет нарастающего итога (Треугольное соединение): Соединение временной таблицы самой с собой по условию Т1.МоментВремени > Т2.МоментВремени имеет квадратичную сложность O(n²). Если в выборке 1000 записей, СУБД придется выполнить до 1 000 000 сравнений.
  4. Отсутствие индексов во временных таблицах: Без явного указания индексов при создании ПОМЕСТИТЬ, последующие соединения (JOIN) будут выполняться крайне медленно, особенно на больших объемах данных.

Шаг 1: Оптимизация выборки из основной таблицы проводок

Вместо использования условия ИЛИ в предложении ГДЕ, которое блокирует эффективное использование индексов, мы применим объединение двух независимых выборок. Это позволит СУБД использовать индекс по СчетДт для первой части и индекс по СчетКт для второй.

Рассмотрим, как переписать этот блок:


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

ОБЪЕДИНИТЬ ВСЕ

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

Такой подход гарантирует "попадание в индекс" (Index Seek), что критически важно для производительности на серверах под управлением PostgreSQL или MS SQL Server.

Шаг 2: Индексация временных таблиц

Это самый простой и эффективный способ ускорить выполнение запроса. Выясним причину: когда мы помещаем данные во временную таблицу втЗапСчетов, платформа 1С создает реальную таблицу в базе данных СУБД. Без индексов любое соединение с этой таблицей будет приводить к Table Scan.

Обязательно индексируем поля, которые участвуют в соединениях (JOIN) и условиях СГРУППИРОВАТЬ ПО. В нашем случае это Регистратор, НомерСтроки, Счет и поля субконто.


ВЫБРАТЬ ...
ПОМЕСТИТЬ втОбЗаПериод
ИЗ ...
ИНДЕКСИРОВАТЬ ПО
    Счет,
    Субконто1,
    Субконто2,
    МоментВремени
;

Как показала практика обсуждения, добавление индексов позволило сократить время выполнения запроса с неопределенного времени до приемлемых 30 секунд за квартал.

Шаг 3: Оптимизация работы с субконто

Обращение к таблице РегистрБухгалтерии.Хозрасчетный.Субконто — очень "дорогая" операция. Чтобы минимизировать нагрузку, проанализируем последовательность действий. Не стоит выбирать все субконто для всех проводок. Сначала максимально ограничим выборку проводок по счетам и периоду, поместим их во временную таблицу, и только потом присоединим субконто.

Также важно помнить, что в физической таблице субконто поле Вид является частью индекса. Поэтому условие по виду субконто (Номенклатура, Склады) должно быть максимально простым и точным.

Шаг 4: Решение проблемы нарастающего итога

Самая сложная часть запроса — расчет остатка на момент каждого документа. Соединение втОбЗаПериод КАК втОбЗаПериод1 с самой собой по условию МоментВремени > МоментВремени1 — это классический "убийца производительности".

Проанализируем альтернативные варианты:

  1. Вынос расчета на сторону встроенного языка 1С: Если количество записей по одной комбинации (Счет + Номенклатура + Склад) не превышает нескольких тысяч, гораздо быстрее будет выгрузить обороты в ТаблицаЗначений, отсортировать ее и рассчитать остатки простым циклом Для Каждого. Процессор сервера приложений справится с этим за миллисекунды, в то время как СУБД может "задохнуться" на вложенных циклах.
  2. Использование пакетного запроса с накоплением: Если необходимо остаться в рамках одного запроса, убедитесь, что временная таблица перед этим шагом максимально "худая" (содержит только агрегированные данные, а не все поля регистра).

Пример оптимизированного соединения для нарастающего итога с предварительной группировкой:


ВЫБРАТЬ
    Т1.Счет,
    Т1.Субконто1,
    Т1.Субконто2,
    Т1.Регистратор,
    СУММА(Т2.Количество) КАК НарастающийОстаток
ИЗ
    втОбороты КАК Т1
ВНУТРЕННЕЕ СОЕДИНЕНИЕ втОбороты КАК Т2
    ПО Т1.Счет = Т2.Счет
    И Т1.Субконто1 = Т2.Субконто1
    И Т1.Субконто2 = Т2.Субконто2
    И Т1.МоментВремени >= Т2.МоментВремени
СГРУППИРОВАТЬ ПО
    Т1.Счет,
    Т1.Субконто1,
    Т1.Субконто2,
    Т1.Регистратор

Шаг 5: Особенности работы с PostgreSQL

Если ваша база работает на PostgreSQL, помните о важности статистики. При интенсивном использовании временных таблиц планировщик запросов может ошибаться, считая, что в таблице 0 или 1 запись. Это приводит к выбору неоптимального плана (например, постоянному использованию Nested Loops там, где нужен Hash Join).

Посмотрим на рекомендации для этой СУБД: - Используйте ВЫРАЗИТЬ для приведения типов в соединениях, чтобы избежать неявного преобразования типов (Implicit Cast), которое не позволяет использовать индексы. - Разделяйте один огромный запрос на несколько вызовов метода Запрос.Выполнить(). Это позволит СУБД актуализировать статистику между этапами (хотя в рамках одного пакета 1С это не всегда эффективно, разделение на разные пакеты в коде помогает).

Итоги и рекомендации

Рассмотрев проблему, мы выяснили, что "битые" итоги в базе данных — это серьезная техническая проблема, которую лучше всего решать штатным средством: Тестирование и исправление -> Пересчет итогов. Однако, если вам крайне необходимо получить данные из физических таблиц, соблюдайте следующие правила:

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

← На главную