В конфигурациях 1С:Предприятие 7.7 (в частности, «Торговля и Склад 7.7») часто возникает задача вывода оперативных остатков товара прямо в форме подбора или списке справочника «Номенклатура». Если для каждой отображаемой строки вызывать функцию, внутри которой выполняется расчет через метод СводныйОстаток() или Остаток() регистра, список начинает заметно притормаживать при прокрутке и навигации.
Выясним причину этой проблемы и рассмотрим несколько эффективных способов ускорения работы формы: от штатного кэширования данных в оперативной памяти до использования прямых SQL-запросов.
Проанализируем механику работы формы 1С 7.7:
Регистр.СводныйОстаток() платформа обращается к дисковым файлам базы данных (таблицам итогов RG*.DBF). Если обращений сотни в секунду, дисковая подсистема и файловые блокировки создают сильные задержки.Фирма) в вызове не заполнено, а отбор идет по второму (Номенклатура) и третьему (Склад), платформе приходится сканировать всю таблицу итогов перебором.Наиболее простое и производительное решение в рамках типовой архитектуры — выгрузить все актуальные итоги регистра один раз при открытии формы в оперативную память, а в строках списка выполнять быстрый поиск по индексу.
Разберем по шагам реализацию этого механизма:
ПриОткрытии() выгружаем остатки регистра через метод ВыгрузитьИтоги().ИндексированнаяТаблица (компонента 1C++ / FormEx) или штатную ТаблицаЗначений.Номенклатура и Склад.Посмотрим на пример кода инициализации и поиска в ИТЗ:
// В модуле формы справочника (для подбора)
Перем ИндексТабОстатков;
Процедура ПриОткрытии()
РегОст = СоздатьОбъект("Регистр.ОстаткиТМЦ");
// Создаем объект индексированной таблицы
ИндексТабОстатков = СоздатьОбъект("ИндексированнаяТаблица");
// Выгружаем остатки на точку актуальности
РегОст.ВыгрузитьИтоги(ИндексТабОстатков, 1, 1);
// Создаем составной индекс по измерениям
ИндексТабОстатков.ДобавитьИндекс("ПоискОстатка", "Номенклатура*, Склад*");
КонецПроцедуры
Функция ПолучитьОстатокПоСкладу(ТекНоменклатура, ТекСклад)
Если ПустоеЗначение(ТекНоменклатура) = 1 Тогда
Возврат 0;
КонецЕсли;
СписокПараметров = СоздатьОбъект("СписокЗначений");
СписокПараметров.ДобавитьЗначение(ТекНоменклатура);
СписокПараметров.ДобавитьЗначение(ТекСклад);
// Быстрый поиск в памяти по индексу
Если ИндексТабОстатков.НайтиСтроку("ПоискОстатка", СписокПараметров, , 1) = 1 Тогда
Возврат ИндексТабОстатков.Количество;
Иначе
Возврат 0;
КонецЕсли;
КонецФункции
Важный нюанс: при создании индекса в объекте ИндексированнаяТаблица для колонок со ссылочными типами данных следует добавлять знак * после имени колонки (например, "Номенклатура*, Склад*"), чтобы индексация выполнялась корректно по внутренним идентификаторам объектов.
Если база данных работает в файловом режиме (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) перед серией вызовов остатков.ТекущийЭлемент()).