Как распределить позиции накладной по остаткам на складах с помощью запроса в 1С?

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

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

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

Метод интервалов (линеек) для распределения в запросе

Поскольку в языке запросов 1С отсутствуют нативные оконные функции (такие как SUM() OVER () в стандартном T-SQL), получение нарастающего итога реализуется путем самосоединения таблиц. Чтобы найти пересечение требуемого количества и имеющихся остатков, сформируем для каждой сущности (склада и потребности) так называемую «линейку» — диапазон значений КоличествоОт и КоличествоДо.

Разберем алгоритм построения интервалов на примере следующих временных таблиц:

  1. Временная таблица складов (остатков): содержит список складов с доступным положительным остатком по номенклатуре.
  2. Временная таблица товаров (потребности): содержит список товаров и необходимое к списанию количество.

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


// 1. Формирование линейки интервалов по складам (остаткам)
ВЫБРАТЬ
    Склады.Склад КАК Склад,
    СУММА(Слева.Количество) - МИНИМУМ(Склады.Количество) КАК КоличествоОт,
    СУММА(Слева.Количество) КАК КоличествоДо
ПОМЕСТИТЬ ЛинейкаСкладов
ИЗ
    ТаблицаОстатков КАК Склады
        ВНУТРЕННЕЕ СОЕДИНЕНИЕ ТаблицаОстатков КАК Слева
        ПО Слева.Склад <= Склады.Склад
СГРУППИРОВАТЬ ПО
    Склады.Склад
;

////////////////////////////////////////////////////////////////////////////////
// 2. Формирование линейки интервалов по потребностям (товарам)
ВЫБРАТЬ
    Товары.Товар КАК Товар,
    СУММА(Слева.Количество) - МИНИМУМ(Товары.Количество) КАК КоличествоОт,
    СУММА(Слева.Количество) КАК КоличествоДо
ПОМЕСТИТЬ ЛинейкаТоваров
ИЗ
    ТаблицаТоваров КАК Товары
        ВНУТРЕННЕЕ СОЕДИНЕНИЕ ТаблицаТоваров КАК Слева
        ПО Слева.Товар <= Товары.Товар
СГРУППИРОВАТЬ ПО
    Товары.Товар
;

////////////////////////////////////////////////////////////////////////////////
// 3. Распределение количества путем соединения линеек по пересечению интервалов
ВЫБРАТЬ
    Склады.Склад КАК Склад,
    Товары.Товар КАК Товар,
    ВЫБОР
        КОГДА Товары.КоличествоДо < Склады.КоличествоДо
            ТОГДА Товары.КоличествоДо
        ИНАЧЕ Склады.КоличествоДо
    КОНЕЦ - ВЫБОР
        КОГДА Товары.КоличествоОт > Склады.КоличествоОт
            ТОГДА Товары.КоличествоОт
        ИНАЧЕ Склады.КоличествоОт
    КОНЕЦ КАК Количество
ИЗ
    ЛинейкаСкладов КАК Склады
        ВНУТРЕННЕЕ СОЕДИНЕНИЕ ЛинейкаТоваров КАК Товары
        ПО Склады.КоличествоОт < Товары.КоличествоДо
            И Склады.КоличествоДо > Товары.КоличествоОт
;

В третьей части запроса происходит наложение интервалов друг на друга. Условие соединения ПО Склады.КоличествоОт < Товары.КоличествоДо И Склады.КоличествоДо > Товары.КоличествоОт находит пересекающиеся отрезки, а конструкция ВЫБОР КОГДА ... КОНЕЦ вычисляет точную величину этого пересечения (длину общего отрезка).

Учет отрицательных остатков и расчет дефицита

При использовании этого метода важно учитывать две технические особенности:

  1. Отрицательные остатки: если на одном из складов числится отрицательный остаток (например, -2 штуки из-за ошибок учета или распроведения документов), это нарушит монотонность нарастающего итога. В результате upper-bound интервала станет меньше lower-bound, и логика запроса выдаст некорректные данные.
    Решение: Перед построением линеек необходимо отфильтровать отрицательные остатки с помощью условия ГДЕ Остаток > 0 или явно обнулить их через ВЫБОР КОГДА Остаток < 0 ТОГДА 0 ИНАЧЕ Остаток КОНЕЦ.
  2. Определение дефицита: чтобы узнать, какого товара и сколько не хватает, необходимо сравнить общее требуемое количество с суммой всех доступных положительных остатков. Это можно сделать дополнительным пакетом запроса:
    Дефицит = МАКСИМУМ(ТребуемоеКоличество - СуммаПоложительныхОстатков, 0).
  3. Уникальность порядка строк: при соединении таблиц по условию <= важно гарантировать уникальность полей порядка. Если на разных складах одинаковое наименование номенклатуры или одинаковые коды, условия сравнения по строкам могут привести к дублированию. Для строк накладной используйте НомерСтроки, а для складов — уникальный составной ключ или индекс.

Ограничения производительности и альтернативы

Проанализируем производительность данного метода. Внутреннее самосоединение для формирования нарастающего итога обладает квадратичной вычислительной сложностью O(N²).

Если задача выполняется для небольшой накладной (десяток строк) и ограниченного количества складов (5–10 складов), то запрос отработает быстро. Но если мы попытаемся таким образом распределить документ на сотни строк по десяткам складов, нагрузка на СУБД резко возрастает.

Для снижения нагрузки в запросах обязательна индексация временных таблиц:


ПОМЕСТИТЬ ВТ_ОстаткиСкладов
ИЗ ...
ИНДЕКСИРОВАТЬ ПО Номенклатура, Склад
;

Вывод и рекомендации:

Если распределение требуется выполнять в отчетах на СКД, использование метода интервалов в запросе полностью оправдано. Однако для оперативного проведения документов и массовых обработок в коде BSL рекомендуется использовать классический алгоритм обхода ТаблицыЗначений в цикле. Алгоритм на BSL имеет линейную сложность O(N), работает значительно быстрее и намного проще обрабатывает отрицательные остатки и дефицит.

← На главную