Как одним запросом подобрать остатки товара с нескольких складов в 1С

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

При автоматизации складского учета и проведения документов списания часто возникает задача: необходимо подобрать требуемое количество номенклатуры с разных складов до тех пор, пока полностью не закроется потребность. Например, требуется списать 27 штук товара. На складе Х есть 18 штук, на складе Y — 3 штуки, на складе Z — 5 штук, а на складе T — 9 штук. В результате списания нужно забрать 18 штук со склада X, 3 штуки со склада Y, 5 штук со склада Z и оставшиеся 1 штуку со склада T.

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

Принцип работы: нарастающий итог в запросе

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

Разберем логику построения такого запроса по шагам:

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

Пример реализации запроса с накопительным итогом

Рассмотрим конкретный пример пакета запросов, который распределяет требуемое количество номенклатуры по складам:


ВЫБРАТЬ
	ТоварыНаСкладахОстатки.Склад КАК Склад,
	ТоварыНаСкладахОстатки.КоличествоОстаток КАК КоличествоОстаток
ПОМЕСТИТЬ ОстаткиПоСкладам
ИЗ
	РегистрНакопления.ТоварыНаСкладах.Остатки(, Номенклатура = &Номенклатура И КоличествоОстаток > 0) КАК ТоварыНаСкладахОстатки
;

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

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
	НакопительныйИтог.Склад КАК Склад,
	ОстаткиПоСкладам.КоличествоОстаток КАК КоличествоОписания
ПОМЕСТИТЬ СкладыПолногоСписания
ИЗ
	НакопительныйИтог КАК НакопительныйИтог
		ВНУТРЕННЕЕ СОЕДИНЕНИЕ ОстаткиПоСкладам КАК ОстаткиПоСкладам
		ПО НакопительныйИтог.Склад = ОстаткиПоСкладам.Склад
ГДЕ
	НакопительныйИтог.КоличествоНакоплением < &КоличествоПоиска
;

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

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ Склад, КоличествоОписания ИЗ СкладыПолногоСписания
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ Склад, КоличествоОписания ИЗ ПоследнийСклад

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

Оптимизированный расчет с помощью конструкции ВЫБОР КОГДА

Чтобы избежать большого количества объединений (ОБЪЕДИНИТЬ ВСЕ) и разгрузить пакет запросов, можно применить более универсальный способ с расчетом границы списания прямо в итоговой выборке. Посмотрим на этот подход:


ВЫБРАТЬ
	Т1.Склад КАК Склад,
	Т1.КоличествоОстаток КАК ОстатокНаСкладе,
	СУММА(Т2.КоличествоОстаток) КАК НакопленоВключаяТекущий,
	СУММА(Т2.КоличествоОстаток) - Т1.КоличествоОстаток КАК НакопленоДоТекущего
ПОМЕСТИТЬ НакопленияПоСкладам
ИЗ
	ОстаткиПоСкладам КАК Т1
		ВНУТРЕННЕЕ СОЕДИНЕНИЕ ОстаткиПоСкладам КАК Т2
		ПО Т1.Склад.Код >= Т2.Склад.Код
СГРУППИРОВАТЬ ПО
	Т1.Склад,
	Т1.КоличествоОстаток
;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
	Накопления.Склад КАК Склад,
	ВЫБОР
		КОГДА Накопления.НакопленоВключаяТекущий <= &КоличествоПоиска
			ТОГДА Накопления.ОстатокНаСкладе
		ИНАЧЕ &КоличествоПоиска - Накопления.НакопленоДоТекущего
	КОНЕЦ КАК КоличествоКВзятию
ИЗ
	НакопленияПоСкладам КАК Накопления
ГДЕ
	Накопления.НакопленоДоТекущего < &КоличествоПоиска

Разберем логику работы условного выражения ВЫБОР КОГДА:

  1. Если накопленная сумма включая текущий склад меньше или равна требуемому количеству, мы забираем весь остаток с этого склада.
  2. Если накопленная сумма до текущего склада меньше требуемой, но с учетом текущего склада превышает ее — этот склад является последним в цепочке. С него забирается только остаток потребности (&КоличествоПоиска - НакопленоДоТекущего).
  3. Условие в секции ГДЕ отсекает все последующие склады, на которых потребность уже полностью закрыта.

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

При использовании вычисления нарастающих итогов в запросах 1С необходимо учитывать следующие технические нюансы:

Альтернативные варианты решения

Если задача выполняется в высоконагруженной системе или в рамках проведения документов, стоит рассмотреть альтернативные варианты:

  1. Обход выборки в цикле на сервере: Самый производительный вариант для СУБД. Выбираем остатки одним простым запросом ВЫБРАТЬ Склад, КоличествоОстаток ... УПОРЯДОЧИТЬ ПО ... и распределяем потребность линейным обходом ($O(N)$) в коде 1С.
  2. Система компоновки данных (СКД): В отчетах и обработках СКД умеет вычислять нарастающий итог на стороне платформы 1С без тяжелых SQL-соединений. Для этого используется функция ресурсов или вычисляемых полей ВычислитьВыражение("Сумма(КоличествоОстаток)",,, "Первая", "Текущая").
← На главную