Как создать единую точку получения остатков в 1С для оптимизации множества старых запросов?

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

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

Основная цель — заменить зоопарк из 20+ процедур на единую точку входа, которая будет гибко настраиваться под разные нужды (например, кому-то нужны остатки с учетом серий, кому-то без, кому-то — с разложением до комплектующих) и при этом работать максимально быстро за счет одного обращения к базе данных.

Подход 1: Универсальная функция с параметрами и пакетным запросом

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

Шаг 1. Проектирование параметров функции

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

Шаг 2. Формирование пакетного запроса

Ключевая идея для оптимизации — получить все необходимые данные за одно обращение к базе данных. Для этого идеально подходят пакетные запросы и временные таблицы. Вместо того чтобы вызывать 3-4 разные функции (остатки, резервы, заказы), которые будут выполнять каждая свой запрос, мы соберем все в один пакет.

Рассмотрим примерную структуру такого запроса:

  1. Первый запрос пакета: Получаем фактические остатки товаров из регистра накопления (например, РегистрНакопления.ТоварыНаСкладах) и помещаем их во временную таблицу ВТ_Остатки. На этом этапе мы применяем все основные отборы по номенклатуре, складу и т.д.
  2. Второй запрос пакета: Получаем данные о резервах из соответствующего регистра (например, РегистрНакопления.РезервыТоваров) и помещаем их во временную таблицу ВТ_Резервы.
  3. Третий (и последующие) запросы пакета: Собираем прочую информацию, влияющую на свободный остаток. Это могут быть данные из регистров заказов поставщикам, заказов на перемещение, или даже данные из документов, которые делают движения, но должны учитываться особым образом (например, незавершенная приемка). Каждую порцию данных помещаем в свою временную таблицу (ВТ_ОжидаемыеПоступления, ВТ_ОсобыеСостояния).
  4. Итоговый запрос: Это основной запрос, который объединяет (через ЛЕВОЕ СОЕДИНЕНИЕ) все созданные временные таблицы. Здесь мы получаем данные из ВТ_Остатки и вычитаем из них данные из ВТ_Резервы, прибавляем ВТ_ОжидаемыеПоступления и т.д. В результате получаем финальную таблицу со всеми рассчитанными показателями.

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

Шаг 3. Динамическая сборка текста запроса

Поскольку требования к детализации могут меняться (например, нужны или не нужны серии), текст запроса нужно формировать динамически. Внутри нашей универсальной функции мы анализируем переданные параметры и "собираем" текст запроса как конструктор.

Посмотрим на условный пример:


Функция ПолучитьОстатки(Параметры) Экспорт

    Запрос = Новый Запрос;
    ТекстЗапроса = "";
    
    // --- Пакет 1: Фактические остатки ---
    ТекстЗапроса = ТекстЗапроса + "
    |ВЫБРАТЬ
    |    ТоварыНаСкладахОстатки.Номенклатура,
    |    ТоварыНаСкладахОстатки.Склад,
    |    СУММА(ТоварыНаСкладахОстатки.КоличествоОстаток) КАК Количество
    |{ВЫБРАТЬ
    |    Номенклатура.*,
    |    Склад.*,
    |    Количество}
    |ПОМЕСТИТЬ ВТ_Остатки
    |ИЗ
    |    РегистрНакопления.ТоварыНаСкладах.Остатки(&Дата, {УСЛОВИЕ}) КАК ТоварыНаСкладахОстатки
    |СГРУППИРОВАТЬ ПО
    |    ТоварыНаСкладахОстатки.Номенклатура,
    |    ТоварыНаСкладахОстатки.Склад
    |;
    |";

    // --- Пакет 2: Резервы ---
    ТекстЗапроса = ТекстЗапроса + "
    |////////////////////////////////////////////////////////////////////////////////
    |ВЫБРАТЬ
    |    РезервыТоваровОстатки.Номенклатура,
    |    РезервыТоваровОстатки.Склад,
    |    СУММА(РезервыТоваровОстатки.КоличествоОстаток) КАК КоличествоРезерв
    |ПОМЕСТИТЬ ВТ_Резервы
    |ИЗ
    |    РегистрНакопления.РезервыТоваров.Остатки(&Дата, {УСЛОВИЕ}) КАК РезервыТоваровОстатки
    |СГРУППИРОВАТЬ ПО
    |    РезервыТоваровОстатки.Номенклатура,
    |    РезервыТоваровОстатки.Склад
    |;
    |";
    
    // --- Итоговый запрос ---
    ТекстЗапроса = ТекстЗапроса + "
    |////////////////////////////////////////////////////////////////////////////////
    |ВЫБРАТЬ
    |    Остатки.Номенклатура,
    |    Остатки.Склад,
    |    ЕСТЬNULL(Остатки.Количество, 0) КАК Остаток,
    |    ЕСТЬNULL(Резервы.КоличествоРезерв, 0) КАК ВРезерве,
    |    ЕСТЬNULL(Остатки.Количество, 0) - ЕСТЬNULL(Резервы.КоличествоРезерв, 0) КАК СвободныйОстаток
    |ИЗ
    |    ВТ_Остатки КАК Остатки
    |        ЛЕВОЕ СОЕДИНЕНИЕ ВТ_Резервы КАК Резервы
    |        ПО Остатки.Номенклатура = Резервы.Номенклатура
    |            И Остатки.Склад = Резервы.Склад
    |";
    
    // Динамически добавляем условия и разрезы на основе структуры Параметры
    // Например, если Параметры.Отбор.Склад Заполнено, то устанавливаем параметр запроса
    Запрос.УстановитьПараметр("Дата", Параметры.Дата);
    // ...установка других параметров
    
    Запрос.Текст = ТекстЗапроса;
    
    Результат = Запрос.Выполнить().Выгрузить();
    Возврат Результат;

КонецФункции

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

Подход 2: Регистр сведений для "снэпшотов" остатков

Для высоконагруженных систем, где скорость получения остатков критична (например, в формах подбора, где данные обновляются при каждом действии пользователя), расчет остатков "на лету" может быть слишком медленным. В таких случаях применяют альтернативный архитектурный подход.

Суть метода:

Создается периодический РегистрСведений (независимый), в который записываются уже рассчитанные данные по остаткам. Например, регистр ОперативныеОстатки с измерениями (Номенклатура, Склад, Характеристика) и ресурсами (Остаток, Резерв, СвободныйОстаток).

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

Плюсы:

Минусы:

Этот подход хорошо зарекомендовал себя в типовых конфигурациях (например, в УТ) и является мощным инструментом оптимизации, но требует более высокой квалификации для внедрения и поддержки.

Общие рекомендации

Независимо от выбранного подхода, придерживайтесь следующих правил:

  1. Профилируйте производительность. Прежде чем бросаться в сложную оптимизацию, используйте "Замер времени", чтобы найти самые медленные участки. Возможно, проблема не в количестве запросов, а в одном "тяжелом" запросе без нужных индексов.
  2. Проверьте индексы. Убедитесь, что по полям, которые используются в условиях (ГДЕ) и соединениях (ПО) запросов, в базе данных созданы индексы.
  3. Избегайте "универсального монстра". Вместо одной гигантской функции, которая пытается учесть абсолютно все возможные сценарии, может быть целесообразнее создать 2-3 более специализированные, но работающие по единому принципу (например, через общий механизм пакетных запросов). Это упростит отладку и поддержку.

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

← На главную