Как оптимизировать получение и отображение остатков в форме подбора 1С 7.7

Программист 1С v7.7 1С:Управление торговлей Управленческий учет Торговля и дистрибуция
← На главную

В конфигурациях 1С:Предприятие 7.7 (в частности, «Торговля и Склад 7.7») часто возникает задача вывода оперативных остатков товара прямо в форме подбора или списке справочника «Номенклатура». Если для каждой отображаемой строки вызывать функцию, внутри которой выполняется расчет через метод СводныйОстаток() или Остаток() регистра, список начинает заметно притормаживать при прокрутке и навигации.

Выясним причину этой проблемы и рассмотрим несколько эффективных способов ускорения работы формы: от штатного кэширования данных в оперативной памяти до использования прямых SQL-запросов.

Почему тормозит расчет остатков на форме списка

Проанализируем механику работы формы 1С 7.7:

  1. Многократный построчный вызов формулы: Функция, прописанная в свойствах текстовой колонки или в элементе диалога на форме, вызывается платформой синхронно при каждой перерисовке строки, каждом перемещении курсора и скроллинге.
  2. Постоянное чтение файлов итогов: При каждом вызове метода Регистр.СводныйОстаток() платформа обращается к дисковым файлам базы данных (таблицам итогов RG*.DBF). Если обращений сотни в секунду, дисковая подсистема и файловые блокировки создают сильные задержки.
  3. Порядок измерений в регистре: Поиск по таблице итогов использует составной индекс, построенный строго в порядке измерений конфигуратора. Если первое измерение (например, Фирма) в вызове не заполнено, а отбор идет по второму (Номенклатура) и третьему (Склад), платформе приходится сканировать всю таблицу итогов перебором.

Способ 1. Штатное кэширование итогов в Индексированную таблицу значений (ИТЗ)

Наиболее простое и производительное решение в рамках типовой архитектуры — выгрузить все актуальные итоги регистра один раз при открытии формы в оперативную память, а в строках списка выполнять быстрый поиск по индексу.

Разберем по шагам реализацию этого механизма:

  1. В процедуре ПриОткрытии() выгружаем остатки регистра через метод ВыгрузитьИтоги().
  2. Загружаем полученные данные в объект ИндексированнаяТаблица (компонента 1C++ / FormEx) или штатную ТаблицаЗначений.
  3. Строим индекс по колонкам Номенклатура и Склад.
  4. В функции отображения остатка ищем строку в памяти по индексу вместо обращения к регистрам на диске.
  5. Добавляем на форму кнопку ручного обновления остатков на случай, если данные изменились во время открытого сеанса подбора.

Посмотрим на пример кода инициализации и поиска в ИТЗ:


// В модуле формы справочника (для подбора)
Перем ИндексТабОстатков;

Процедура ПриОткрытии()
    РегОст = СоздатьОбъект("Регистр.ОстаткиТМЦ");
    
    // Создаем объект индексированной таблицы
    ИндексТабОстатков = СоздатьОбъект("ИндексированнаяТаблица");
    
    // Выгружаем остатки на точку актуальности
    РегОст.ВыгрузитьИтоги(ИндексТабОстатков, 1, 1);
    
    // Создаем составной индекс по измерениям
    ИндексТабОстатков.ДобавитьИндекс("ПоискОстатка", "Номенклатура*, Склад*");
КонецПроцедуры

Функция ПолучитьОстатокПоСкладу(ТекНоменклатура, ТекСклад)
    Если ПустоеЗначение(ТекНоменклатура) = 1 Тогда
        Возврат 0;
    КонецЕсли;
    
    СписокПараметров = СоздатьОбъект("СписокЗначений");
    СписокПараметров.ДобавитьЗначение(ТекНоменклатура);
    СписокПараметров.ДобавитьЗначение(ТекСклад);
    
    // Быстрый поиск в памяти по индексу
    Если ИндексТабОстатков.НайтиСтроку("ПоискОстатка", СписокПараметров, , 1) = 1 Тогда
        Возврат ИндексТабОстатков.Количество;
    Иначе
        Возврат 0;
    КонецЕсли;
КонецФункции

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

Способ 2. Получение остатков прямым запросом (1SQLite / 1C++)

Если база данных работает в файловом режиме (DBF) или на MS SQL Server, для мгновенной выборки данных без блокировок можно использовать прямой SQL-запрос с помощью внешней компоненты 1SQLite.

Рассмотрим, как устроен расчет остатка на точку актуальности (ТА). Данные берутся из таблицы итогов за последний закрытый период (начало месяца ТА):


// Пример получения остатков на начало расчетного периода через 1SQLite
ЗапросSQL = СоздатьОбъект("1SQLite");

ТекстЗапроса = "
|SELECT
|    Остатки.Номенклатура As [Номенклатура :Справочник.Номенклатура],
|    Остатки.Склад As [Склад :Справочник.Склады],
|    SUM(Остатки.Количество) As Количество
|FROM
|    [РегистрИтоги.ОстаткиТМЦ] As Остатки
|WHERE
|    Остатки.PERIOD = :НачПериода
|GROUP BY
|    Остатки.Номенклатура, Остатки.Склад
|HAVING
|    SUM(Остатки.Количество) <> 0
|";

МД = СоздатьОбъект("MetaDataWork");
НачПериода = МД.ПолучитьНачПериода(ПолучитьДатуТА());

ЗапросSQL.Подставлять("НачПериода", НачПериода);
ТаблицаИтогов = ЗапросSQL.ВыполнитьЗапрос(ТекстЗапроса);

Если остатки требуется рассчитать на произвольную дату или момент времени внутри месяца, запрос должен объединять итоги на начало месяца и обороты движений из таблицы [Регистр.ОстаткиТМЦ] до нужной даты:


// Учет движений при расчете остатка на произвольную дату
ТекстЗапроса = "
|SELECT
|    Движения.Номенклатура As [Номенклатура :Справочник.Номенклатура],
|    Движения.Склад As [Склад :Справочник.Склады],
|    SUM(CASE WHEN Движения.DEBKRED = 0 THEN Движения.Количество ELSE -Движения.Количество END) As Оборот
|FROM
|    [Регистр.ОстаткиТМЦ] As Движения
|INNER JOIN [Журнал] As Жур ON Жур.iddoc = Движения.iddoc
|WHERE
|    Жур.date BETWEEN :НачДата AND :КонДата
|GROUP BY
|    Движения.Номенклатура, Движения.Склад
|";

Рекомендации по оптимизации без внешних компонентов

Если нет возможности подключать сторонние библиотеки, оптимизировать штатную работу можно следующими приемами:

  1. Инициализация объекта регистра на уровне модуля: Создавайте объект СоздатьОбъект("Регистр.ОстаткиТМЦ") один раз в процедуре ПриОткрытии() и сохраняйте его в модульной переменной, чтобы избежать постоянного выделения памяти внутри функции колонки.
  2. Использование пакетного расчета: Применяйте метод Рег.ВременныйРасчет(1) перед серией вызовов остатков.
  3. Перенос расчета в фокус текущей строки: Не выводите остатки в каждую ячейку общей таблицы. Гораздо быстрее отображать детальные остатки по складам в отдельной таблице или текстовых полях внизу формы только для текущей выбранной номенклатуры (по значению ТекущийЭлемент()).
← На главную