Как оптимизировать запрос динамического списка номенклатуры для отображения остатков по складам?

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

Вывод оперативных остатков в форме списка справочника Номенклатура — одна из самых частых задач в конфигурациях 1С, таких как «Управление торговлей» или «ERP Управление предприятием». Однако неверная реализация запроса динамического списка приводит к резкому падению производительности: форма зависает при открытии, прокрутка страниц дергается, а сервер СУБД испытывает колоссальные нагрузки. Проанализируем типичные ошибки в запросе динамического списка и подробно разберем рабочие варианты решения проблемы.

В чем причина медленной работы исходного запроса

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

Разберем по шагам ключевые критические ошибки такого построения запроса:

  1. Использование таблицы ОстаткиИОбороты вместо Остатки. Виртуальная таблица остатков и оборотов предназначена для анализа движений за определенный период. Она соединяет таблицы движений и итогов, что требует колоссальных ресурсов СУБД. Для получения текущего наличия необходимо обращаться исключительно к виртуальной таблице Остатки.
  2. Использование конструкции ПОЛНОЕ СОЕДИНЕНИЕ. Полное соединение делает невозможным использование индексов ведущей таблицы и блокирует внутреннюю оптимизацию динамического списка. Платформа 1С не может применить постраничную выборку (механизм порционной подгрузки строк TOP / FETCH FIRST), вынуждая СУБД строить огромные промежуточные таблицы во временной базе на каждый шаг прокрутки.
  3. Множественные соединения к одному и тому же регистру. Автор обращается к регистру ТоварыНаСкладах шесть раз с разными параметрами виртуальной таблицы. В результате на уровне SQL генерируется крайне тяжелый план выполнения с повторным сканированием таблиц итогов регистра.
  4. Потеря преимуществ свойства «Основная таблица». Если в динамическом списке используются тяжелые соединения, группировки или сложные вычисляемые поля, платформа отключает быстрый контекстный поиск по началу строки и кэширование ссылочных данных.

Способ 1. Оптимизация запроса в рамках одной таблицы остатков

Если бизнес-требования непременно обязывают видеть колонки остатков по складам непосредственно в списке, проанализируем ситуацию и перепишем запрос. Вместо многократного соединения с одной и той же виртуальной таблицей обратимся к ней ровно один раз, используя ЛЕВОЕ СОЕДИНЕНИЕ, а остатки по конкретным складам разнесем через агрегатные функции и конструкцию ВЫБОР.

Посмотрим на пример оптимизированного текста запроса:


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

Рассмотрим подробнее изменения: мы заменили ПОЛНОЕ СОЕДИНЕНИЕ на ЛЕВОЕ СОЕДИНЕНИЕ, избавились от дублирования обращений к регистру и перешли на легкую таблицу Остатки. Однако следует учитывать: наличие предложения СГРУППИРОВАТЬ ПО все еще снижает эффективность динамического считывания данных на больших справочниках.

Способ 2. Использование события «ПриПолученииДанныхНаСервере»

Наиболее правильный с точки зрения стандартов разработки 1С способ — полностью убрать расчет остатков из основного запроса динамического списка. Платформа предоставляет специализированное событие формы ПриПолученииДанныхНаСервере.

Разберем по шагам, как устроен этот механизм:

  1. В свойствах динамического списка оставляем чистый и быстрый запрос к таблице Справочник.Номенклатура с указанием свойства Основная таблица. Все соединения с регистрами удаляем.
  2. В динамический список добавляем пользовательские колонки для остатков (например, через элементы формы или вычисляемые поля с фиктивными значениями).
  3. Создаем обработчик события ПриПолученииДанныхНаСервере для таблицы формы, отображающей список.
  4. Платформа вызывает этот обработчик только для тех 20–40 строк, которые фактически отображаются на экране пользователя в данный момент.
  5. Методом Строки.ПолучитьКлючи() получаем массив ссылок номенклатуры видимой области.
  6. Выполняем один точечный запрос к таблице РегистрНакопления.ТоварыНаСкладах.Остатки с фильтром ГДЕ Номенклатура В (&МассивНоменклатуры).
  7. Заполняем полученными числами поля в переданной коллекции строк.

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

Способ 3. Переход к архитектуре «Мастер — Деталь»

Проанализируем сценарии реальной работы менеджеров. При поиске товара сотруднику редко требуется видеть остатки по десяткам складов для всего каталога одновременно. Задача менеджера — найти конкретную запчасть или товар и быстро оценить ее доступность.

Вместо перегрузки формы горизонтальными колонками эффективнее разделить интерфейс:

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

Такой подход снимает любую нагрузку с механизма прокрутки списка и исключает проблемы с масштабированием при увеличении числа складов в компании.

Способ 4. Использование агрегирующего регистра сведений

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

  1. Создается непериодический независимый регистр сведений (например, ОстаткиТоваровДляСписков), измерениями которого являются Номенклатура и Склад, а ресурсом — ВНаличии.
  2. Данные регистра обновляются регламентным заданием по расписанию (раз в 5–15 минут) либо в фоновом режиме при проведении документов движения.
  3. В динамическом списке номенклатура соединяется с физической таблицей этого регистра сведений.

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

← На главную