При автоматизации складского учета и проведения документов списания часто возникает задача: необходимо подобрать требуемое количество номенклатуры с разных складов до тех пор, пока полностью не закроется потребность. Например, требуется списать 27 штук товара. На складе Х есть 18 штук, на складе Y — 3 штуки, на складе Z — 5 штук, а на складе T — 9 штук. В результате списания нужно забрать 18 штук со склада X, 3 штуки со склада Y, 5 штук со склада Z и оставшиеся 1 штуку со склада T.
Классический подход подразумевает выборку всех остатков запросом с последующим обходом в цикле на стороне платформы 1С. Однако эту задачу можно решить напрямую в языке запросов 1С без использования программных циклов. Рассмотрим подробно, как это реализовать.
Проанализируем механику решения. Чтобы понять, с какого склада и сколько товара нужно забрать, в запросе необходимо посчитать накопительный (нарастающий) итог остатков. Нарастающий итог рассчитывается путем соединения временной таблицы остатков с самой собой по условию "меньше или равно" или "больше или равно".
Разберем логику построения такого запроса по шагам:
ОстаткиПоСкладам.ОстаткиПоСкладам с самой собой (внутреннее соединение) для расчета суммы остатков с накоплением по каждому складу.Рассмотрим конкретный пример пакета запросов, который распределяет требуемое количество номенклатуры по складам:
ВЫБРАТЬ
ТоварыНаСкладахОстатки.Склад КАК Склад,
ТоварыНаСкладахОстатки.КоличествоОстаток КАК КоличествоОстаток
ПОМЕСТИТЬ ОстаткиПоСкладам
ИЗ
РегистрНакопления.ТоварыНаСкладах.Остатки(, Номенклатура = &Номенклатура И КоличествоОстаток > 0) КАК ТоварыНаСкладахОстатки
;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
ОстаткиПоСкладам.Склад КАК Склад,
СУММА(ОстаткиПоСкладам1.КоличествоОстаток) КАК КоличествоНакоплением
ПОМЕСТИТЬ НакопительныйИтог
ИЗ
ОстаткиПоСкладам КАК ОстаткиПоСкладам
ВНУТРЕННЕЕ СОЕДИНЕНИЕ ОстаткиПоСкладам КАК ОстаткиПоСкладам1
ПО ОстаткиПоСкладам.КоличествоОстаток <= ОстаткиПоСкладам1.КоличествоОстаток
СГРУППИРОВАТЬ ПО
ОстаткиПоСкладам.Склад
;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
НакопительныйИтог.Склад КАК Склад,
ОстаткиПоСкладам.КоличествоОстаток КАК КоличествоОписания
ПОМЕСТИТЬ СкладыПолногоСписания
ИЗ
НакопительныйИтог КАК НакопительныйИтог
ВНУТРЕННЕЕ СОЕДИНЕНИЕ ОстаткиПоСкладам КАК ОстаткиПоСкладам
ПО НакопительныйИтог.Склад = ОстаткиПоСкладам.Склад
ГДЕ
НакопительныйИтог.КоличествоНакоплением < &КоличествоПоиска
;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
НакопительныйИтог.Склад КАК Склад,
ОстаткиПоСкладам.КоличествоОстаток - (НакопительныйИтог.КоличествоНакоплением - &КоличествоПоиска) КАК КоличествоОписания
ПОМЕСТИТЬ ПоследнийСклад
ИЗ
НакопительныйИтог КАК НакопительныйИтог
ВНУТРЕННЕЕ СОЕДИНЕНИЕ ОстаткиПоСкладам КАК ОстаткиПоСкладам
ПО НакопительныйИтог.Склад = ОстаткиПоСкладам.Склад
ГДЕ
НакопительныйИтог.КоличествоНакоплением >= &КоличествоПоиска
И (НакопительныйИтог.КоличествоНакоплением - ОстаткиПоСкладам.КоличествоОстаток) < &КоличествоПоиска
;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ Склад, КоличествоОписания ИЗ СкладыПолногоСписания
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ Склад, КоличествоОписания ИЗ ПоследнийСклад
В данном запросе сначала списывается товар со складов с наибольшим остатком. Если требуется изменить приоритет (например, списывать сначала с мелких складов или по алфавиту), достаточно изменить условие соединения во втором пакете запроса.
Чтобы избежать большого количества объединений (ОБЪЕДИНИТЬ ВСЕ) и разгрузить пакет запросов, можно применить более универсальный способ с расчетом границы списания прямо в итоговой выборке. Посмотрим на этот подход:
ВЫБРАТЬ
Т1.Склад КАК Склад,
Т1.КоличествоОстаток КАК ОстатокНаСкладе,
СУММА(Т2.КоличествоОстаток) КАК НакопленоВключаяТекущий,
СУММА(Т2.КоличествоОстаток) - Т1.КоличествоОстаток КАК НакопленоДоТекущего
ПОМЕСТИТЬ НакопленияПоСкладам
ИЗ
ОстаткиПоСкладам КАК Т1
ВНУТРЕННЕЕ СОЕДИНЕНИЕ ОстаткиПоСкладам КАК Т2
ПО Т1.Склад.Код >= Т2.Склад.Код
СГРУППИРОВАТЬ ПО
Т1.Склад,
Т1.КоличествоОстаток
;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
Накопления.Склад КАК Склад,
ВЫБОР
КОГДА Накопления.НакопленоВключаяТекущий <= &КоличествоПоиска
ТОГДА Накопления.ОстатокНаСкладе
ИНАЧЕ &КоличествоПоиска - Накопления.НакопленоДоТекущего
КОНЕЦ КАК КоличествоКВзятию
ИЗ
НакопленияПоСкладам КАК Накопления
ГДЕ
Накопления.НакопленоДоТекущего < &КоличествоПоиска
Разберем логику работы условного выражения ВЫБОР КОГДА:
&КоличествоПоиска - НакопленоДоТекущего).ГДЕ отсекает все последующие склады, на которых потребность уже полностью закрыта.При использовании вычисления нарастающих итогов в запросах 1С необходимо учитывать следующие технические нюансы:
Склад.Код или Ссылка), чтобы порядок суммирования был строго фиксированным. Если выполнять соединение по полю КоличествоОстаток, при совпадении остатков на разных складах СУБД может вернуть непредсказуемый результат.Если задача выполняется в высоконагруженной системе или в рамках проведения документов, стоит рассмотреть альтернативные варианты:
ВЫБРАТЬ Склад, КоличествоОстаток ... УПОРЯДОЧИТЬ ПО ... и распределяем потребность линейным обходом ($O(N)$) в коде 1С.ВычислитьВыражение("Сумма(КоличествоОстаток)",,, "Первая", "Текущая").