Вывод оперативных остатков в форме списка справочника Номенклатура — одна из самых частых задач в конфигурациях 1С, таких как «Управление торговлей» или «ERP Управление предприятием». Однако неверная реализация запроса динамического списка приводит к резкому падению производительности: форма зависает при открытии, прокрутка страниц дергается, а сервер СУБД испытывает колоссальные нагрузки. Проанализируем типичные ошибки в запросе динамического списка и подробно разберем рабочие варианты решения проблемы.
Выясним причину, почему исходный запрос автора приводит к зависаниям формы списка при прокрутке. В исходном варианте к справочнику номенклатуры присоединялись таблицы остатков по пяти складам, таблица общих остатков, а также заказы клиентов и поставщиков.
Разберем по шагам ключевые критические ошибки такого построения запроса:
ОстаткиИОбороты вместо Остатки. Виртуальная таблица остатков и оборотов предназначена для анализа движений за определенный период. Она соединяет таблицы движений и итогов, что требует колоссальных ресурсов СУБД. Для получения текущего наличия необходимо обращаться исключительно к виртуальной таблице Остатки.ПОЛНОЕ СОЕДИНЕНИЕ. Полное соединение делает невозможным использование индексов ведущей таблицы и блокирует внутреннюю оптимизацию динамического списка. Платформа 1С не может применить постраничную выборку (механизм порционной подгрузки строк TOP / FETCH FIRST), вынуждая СУБД строить огромные промежуточные таблицы во временной базе на каждый шаг прокрутки.ТоварыНаСкладах шесть раз с разными параметрами виртуальной таблицы. В результате на уровне SQL генерируется крайне тяжелый план выполнения с повторным сканированием таблиц итогов регистра.Если бизнес-требования непременно обязывают видеть колонки остатков по складам непосредственно в списке, проанализируем ситуацию и перепишем запрос. Вместо многократного соединения с одной и той же виртуальной таблицей обратимся к ней ровно один раз, используя ЛЕВОЕ СОЕДИНЕНИЕ, а остатки по конкретным складам разнесем через агрегатные функции и конструкцию ВЫБОР.
Посмотрим на пример оптимизированного текста запроса:
ВЫБРАТЬ
СпрНоменклатура.Ссылка КАК Ссылка,
СпрНоменклатура.Код КАК Код,
СпрНоменклатура.Наименование КАК Наименование,
СпрНоменклатура.Артикул КАК Артикул,
СпрНоменклатура.ВидНоменклатуры КАК ВидНоменклатуры,
СпрНоменклатура.ЕдиницаИзмерения КАК ЕдиницаИзмерения,
СпрНоменклатура.СтавкаНДС КАК СтавкаНДС,
СпрНоменклатура.Родитель КАК Родитель,
СУММА(ЕСТЬNULL(ТоварыНаСкладахОстатки.ВНаличииОстаток, 0)) КАК ОбщийОстаток,
СУММА(ВЫБОР
КОГДА ТоварыНаСкладахОстатки.Склад = &Склад1
ТОГДА ЕСТЬNULL(ТоварыНаСкладахОстатки.ВНаличииОстаток, 0)
ИНАЧЕ 0
КОНЕЦ) КАК ВНаличииОстаток1,
СУММА(ВЫБОР
КОГДА ТоварыНаСкладахОстатки.Склад = &Склад2
ТОГДА ЕСТЬNULL(ТоварыНаСкладахОстатки.ВНаличииОстаток, 0)
ИНАЧЕ 0
КОНЕЦ) КАК ВНаличииОстаток2,
СУММА(ВЫБОР
КОГДА ТоварыНаСкладахОстатки.Склад = &Склад3
ТОГДА ЕСТЬNULL(ТоварыНаСкладахОстатки.ВНаличииОстаток, 0)
ИНАЧЕ 0
КОНЕЦ) КАК ВНаличииОстаток3
ИЗ
Справочник.Номенклатура КАК СпрНоменклатура
ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ТоварыНаСкладах.Остатки КАК ТоварыНаСкладахОстатки
ПО (СпрНоменклатура.Ссылка = ТоварыНаСкладахОстатки.Номенклатура)
ГДЕ
НЕ СпрНоменклатура.ЭтоГруппа
СГРУППИРОВАТЬ ПО
СпрНоменклатура.Ссылка,
СпрНоменклатура.Код,
СпрНоменклатура.Наименование,
СпрНоменклатура.Артикул,
СпрНоменклатура.ВидНоменклатуры,
СпрНоменклатура.ЕдиницаИзмерения,
СпрНоменклатура.СтавкаНДС,
СпрНоменклатура.Родитель
Рассмотрим подробнее изменения: мы заменили ПОЛНОЕ СОЕДИНЕНИЕ на ЛЕВОЕ СОЕДИНЕНИЕ, избавились от дублирования обращений к регистру и перешли на легкую таблицу Остатки. Однако следует учитывать: наличие предложения СГРУППИРОВАТЬ ПО все еще снижает эффективность динамического считывания данных на больших справочниках.
Наиболее правильный с точки зрения стандартов разработки 1С способ — полностью убрать расчет остатков из основного запроса динамического списка. Платформа предоставляет специализированное событие формы ПриПолученииДанныхНаСервере.
Разберем по шагам, как устроен этот механизм:
Справочник.Номенклатура с указанием свойства Основная таблица. Все соединения с регистрами удаляем.ПриПолученииДанныхНаСервере для таблицы формы, отображающей список.Строки.ПолучитьКлючи() получаем массив ссылок номенклатуры видимой области.РегистрНакопления.ТоварыНаСкладах.Остатки с фильтром ГДЕ Номенклатура В (&МассивНоменклатуры).Благодаря этому пролистывание списка становится мгновенным: СУБД легко выбирает очередную порцию строк справочника, а точечный расчет остатков для нескольких десятков позиций выполняется за доли миллисекунды.
Проанализируем сценарии реальной работы менеджеров. При поиске товара сотруднику редко требуется видеть остатки по десяткам складов для всего каталога одновременно. Задача менеджера — найти конкретную запчасть или товар и быстро оценить ее доступность.
Вместо перегрузки формы горизонтальными колонками эффективнее разделить интерфейс:
ПриАктивизацииСтроки (или по отдельной кнопке) выполняется быстрый запрос остатков по выбранной номенклатуре и результат выводится в подчиненную таблицу.Такой подход снимает любую нагрузку с механизма прокрутки списка и исключает проблемы с масштабированием при увеличении числа складов в компании.
Если требования бизнеса непреклонны: пользователям обязательно нужны сортировка и жесткий отбор по колонкам остатков в общем списке, соединение с виртуальной таблицей итогов неизбежно заблокирует масштабируемость системы. В этом случае применяют денормализацию данных:
ОстаткиТоваровДляСписков), измерениями которого являются Номенклатура и Склад, а ресурсом — ВНаличии.Обращение к физической таблице базы данных с правильными индексами выполняется на порядки быстрее, чем постоянная агрегация виртуальных таблиц остатков при каждом перемещении курсора по списку.