В практике разработки на платформе 1С часто возникает задача распределения имеющегося складского запаса по строкам потребности (например, по строкам табличной части заказа покупателя). Ситуация усложняется, когда одна и та же позиция номенклатуры встречается в документе несколько раз, а сворачивать строки нельзя из-за необходимости сохранять исходную привязку: номер строки, ключ связи, заказ-источник или статус обеспечения.
Разберем, как решить эту задачу исключительно средствами языка запросов 1С: распределить доступный остаток по порядку строк и вывести недостающее количество (дефицит) отдельной строкой к заказу.
Проанализируем типовую ситуацию на числовом примере:
Поскольку встроенный язык запросов 1С не поддерживает стандартные оконные функции (такие как SUM(...) OVER (...)), для решения применяется паттерн нарастающего итога через самосоединение временных таблиц.
Рассмотрим алгоритм реализации задачи по шагам:
НомерСтроки) и актуальные остатки товаров.тТаб.НомерСтроки >= тТаб1.НомерСтроки). Это позволяет для каждой текущей строки получить суммарную потребность всех предыдущих строк вместе с текущей.ВЫБОР КОГДА сравниваем имеющийся остаток с нарастающим итогом и определяем, какое количество может покрыть текущую строку.ОБЪЕДИНИТЬ ВСЕ) рассчитываем разницу между общей потребностью и складским остатком для тех позиций, где потребность превышает наличие.Посмотрим на полный текст запроса, реализующий данный алгоритм:
// 1. Исходные потребности (имитация табличной части документа)
ВЫБРАТЬ
1 КАК НомерСтроки,
"Гайка" КАК Номенклатура,
4 КАК Количество
ПОМЕСТИТЬ ВТ_Потребность
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ
2,
"Гайка",
6
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ
3,
"Шайба",
7
;
////////////////////////////////////////////////////////////////////////////////
// 2. Исходные остатки (имитация регистра накопления)
ВЫБРАТЬ
"Гайка" КАК Номенклатура,
8 КАК Остаток
ПОМЕСТИТЬ ВТ_Остатки
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ
"Шайба",
20
;
////////////////////////////////////////////////////////////////////////////////
// 3. Расчет нарастающего итога потребности по каждой строке
ВЫБРАТЬ
Потребность.НомерСтроки КАК НомерСтроки,
Потребность.Номенклатура КАК Номенклатура,
Потребность.Количество КАК Количество,
СУММА(ПотребностьДо.Количество) КАК КоличествоНарастающее
ПОМЕСТИТЬ ВТ_ПотребностьСИтогом
ИЗ
ВТ_Потребность КАК Потребность
ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТ_Потребность КАК ПотребностьДо
ПО Потребность.Номенклатура = ПотребностьДо.Номенклатура
И Потребность.НомерСтроки >= ПотребностьДо.НомерСтроки
СГРУППИРОВАТЬ ПО
Потребность.НомерСтроки,
Потребность.Номенклатура,
Потребность.Количество
;
////////////////////////////////////////////////////////////////////////////////
// 4. Итоговое распределение остатков и расчет строк дефицита
ВЫБРАТЬ
ВТ_ПотребностьСИтогом.НомерСтроки КАК НомерСтроки,
ВТ_ПотребностьСИтогом.Номенклатура КАК Номенклатура,
ВТ_ПотребностьСИтогом.Количество КАК Требуется,
ВЫБОР
КОГДА ЕСТЬNULL(ВТ_Остатки.Остаток, 0) >= ВТ_ПотребностьСИтогом.КоличествоНарастающее
ТОГДА ВТ_ПотребностьСИтогом.Количество
ИНАЧЕ ВЫБОР
КОГДА ЕСТЬNULL(ВТ_Остатки.Остаток, 0) - ВТ_ПотребностьСИтогом.КоличествоНарастающее + ВТ_ПотребностьСИтогом.Количество > 0
ТОГДА ЕСТЬNULL(ВТ_Остатки.Остаток, 0) - ВТ_ПотребностьСИтогом.КоличествоНарастающее + ВТ_ПотребностьСИтогом.Количество
ИНАЧЕ 0
КОНЕЦ
КОНЕЦ КАК Обеспечено
ИЗ
ВТ_ПотребностьСИтогом КАК ВТ_ПотребностьСИтогом
ЛЕВОЕ СОЕДИНЕНИЕ ВТ_Остатки КАК ВТ_Остатки
ПО ВТ_ПотребностьСИтогом.Номенклатура = ВТ_Остатки.Номенклатура
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ
NULL КАК НомерСтроки,
ОбщаяПотребность.Номенклатура КАК Номенклатура,
0 КАК Требуется,
ОбщаяПотребность.ВсегоПотребность - ЕСТЬNULL(ВТ_Остатки.Остаток, 0) КАК Обеспечено
ИЗ
(ВЫБРАТЬ
ВТ_Потребность.Номенклатура КАК Номенклатура,
СУММА(ВТ_Потребность.Количество) КАК ВсегоПотребность
ИЗ
ВТ_Потребность КАК ВТ_Потребность
СГРУППИРОВАТЬ ПО
ВТ_Потребность.Номенклатура) КАК ОбщаяПотребность
ЛЕВОЕ СОЕДИНЕНИЕ ВТ_Остатки КАК ВТ_Остатки
ПО ОбщаяПотребность.Номенклатура = ВТ_Остатки.Номенклатура
ГДЕ
ОбщаяПотребность.ВсегоПотребность > ЕСТЬNULL(ВТ_Остатки.Остаток, 0)
Для более компактного и строгого расчета распределения без вложенных условий ВЫБОР КОГДА можно представить процесс как пересечение двух числовых отрезков:
[НакопленоДо, НакопленоПосле], где НакопленоДо = КоличествоНарастающее - Количество, а НакопленоПосле = КоличествоНарастающее.[0, Остаток].Длина = МАКСИМУМ(0, МИНИМУМ(НакопленоПосле, Остаток) - МАКСИМУМ(НакопленоДо, 0))
Применение такой логики позволяет упростить громоздкие ветвления в выражении ВЫБОР при сложных схемах распределения по нескольким складам или партиям.
Разберем технические особенности, которые необходимо учитывать при использовании нарастающего итога в реальных конфигурациях:
>=) при большом числе строк номенклатуры приводит к лавинообразному росту промежуточных записей. Для единичных документов на десятки строк это незаметно, но при пакетной обработке сотен заказов запрос может создавать высокую нагрузку на СУБД.
ИНДЕКСИРОВАТЬ ПО
Номенклатура,
НомерСтроки
;
Ссылка + НомерСтроки).ЕСТЬNULL(Остаток, 0), иначе позиции, которых вовсе нет на складе, пропадут из итоговой выборки.ТаблицеЗначений выполняется за линейное время $O(N)$. Это работает значительно быстрее на больших объемах данных и не нагружает временную базу данных СУБД (tempdb).