При ведении партионного учета на счете 10 нередко возникает ситуация, когда при инвентаризации выявляется пересортица: на остатках числится одна номенклатурная позиция, распределенная по нескольким партиям, а фактически необходимо перенести эти остатки на другие характеристики или номенклатурные позиции (например, костюмы разных размеров) с соблюдением очередности списания партий по методу ФИФО. Обычно такую задачу решают циклом на встроенном языке 1С, однако рассмотрим, как можно реализовать распределение исключительно средствами языка запросов, какие математические алгоритмы для этого применяются и с какими ограничениями платформы придется столкнуться.
Проанализируем исходные данные задачи. Пусть на складе числится исходная позиция в количестве 100 штук, распределенная по пяти партиям поступления:
По результатам инвентаризации выявлена пересортица, которую требуется распределить:
Согласно принципу ФИФО, первые 20 шт. (Костюм 42р) должны забрать Партию 1 (10 шт.) и часть Партии 2 (10 шт.). Следующие 30 шт. (Костюм 43р) забирают оставшиеся 10 шт. из Партии 2 и 20 шт. из Партии 3. Оставшиеся 50 шт. (Костюм 44р) распределяются на Партию 4 (20 шт.) и Партию 5 (30 шт.).
Рассмотрим практическую реализацию запроса, предложенную в обсуждении. Суть метода заключается в нумерации строк с помощью функции АВТОНОМЕРЗАПИСИ(), вычислении нарастающих сумм для партий источников и для строк получателей через самосоединение, а затем сведении полученных цепочек воедино.
Посмотрим на пример пакетного запроса:
ВЫБРАТЬ
"Партия0" КАК Ссылка,
1 КАК Кол,
"МехКрокодила" КАК Аналитика1
ПОМЕСТИТЬ мТаб
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ
"Партия1",
9,
"МехКрокодила"
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ
"Партия2",
20,
"МехКрокодила"
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ
"Партия3",
20,
"МехКрокодила"
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ
"Партия4",
20,
"МехКрокодила"
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ
"Партия5",
30,
"МехКрокодила"
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ
"Партия6",
30,
"МехКрокодила"
;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
"Шуба1" КАК Ссылка,
20 КАК Кол
ПОМЕСТИТЬ мТабПолучатель
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ
"Шуба2",
30
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ
"Шуба3",
50
;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
мТаб.Ссылка,
мТаб.Кол,
мТаб.Аналитика1,
АВТОНОМЕРЗАПИСИ() КАК НН
ПОМЕСТИТЬ мТаб1
ИЗ
мТаб КАК мТаб
;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
мТаб1.Аналитика1,
мТаб11.НН,
мТаб1.НН КАК Отбор
ПОМЕСТИТЬ мТаб2
ИЗ
мТаб1 КАК мТаб1
ВНУТРЕННЕЕ СОЕДИНЕНИЕ мТаб1 КАК мТаб11
ПО мТаб1.Аналитика1 = мТаб11.Аналитика1
И мТаб1.НН <= мТаб11.НН
;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
СУММА(мТаб1.Кол) КАК Кол,
мТаб1.Аналитика1,
мТаб2.НН
ПОМЕСТИТЬ мТаб3
ИЗ
мТаб2 КАК мТаб2
ВНУТРЕННЕЕ СОЕДИНЕНИЕ мТаб1 КАК мТаб1
ПО мТаб2.Отбор = мТаб1.НН
СГРУППИРОВАТЬ ПО
мТаб2.НН,
мТаб1.Аналитика1
;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
мТабПолучатель.Ссылка,
мТабПолучатель.Кол,
АВТОНОМЕРЗАПИСИ() КАК НН
ПОМЕСТИТЬ мТабПолучатель1
ИЗ
мТабПолучатель КАК мТабПолучатель
;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
мТабПолучатель11.НН КАК НН,
мТабПолучатель1.НН КАК отбор
ПОМЕСТИТЬ мТабПолучатель2
ИЗ
мТабПолучатель1 КАК мТабПолучатель1
ВНУТРЕННЕЕ СОЕДИНЕНИЕ мТабПолучатель1 КАК мТабПолучатель11
ПО мТабПолучатель1.НН <= мТабПолучатель11.НН
;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
СУММА(мТабПолучатель1.Кол) КАК Кол,
мТабПолучатель2.НН
ПОМЕСТИТЬ мТабПолучатель3
ИЗ
мТабПолучатель1 КАК мТабПолучатель1
ВНУТРЕННЕЕ СОЕДИНЕНИЕ мТабПолучатель2 КАК мТабПолучатель2
ПО мТабПолучатель1.НН = мТабПолучатель2.отбор
СГРУППИРОВАТЬ ПО
мТабПолучатель2.НН
;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
мТаб3.НН КАК Материал,
мТаб3.Кол КАК КолМатериал,
мТабПолучатель3.НН КАК Изделие,
мТабПолучатель3.Кол КАК КолИзделие
ПОМЕСТИТЬ мТабПеребор
ИЗ
мТаб3 КАК мТаб3
ВНУТРЕННЕЕ СОЕДИНЕНИЕ мТаб1 КАК мТаб1
ПО мТаб3.НН = мТаб1.НН
ВНУТРЕННЕЕ СОЕДИНЕНИЕ мТабПолучатель3 КАК мТабПолучатель3
ПО (мТаб3.Кол - мТаб1.Кол < мТабПолучатель3.Кол)
;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
мТабПеребор.Изделие,
МАКСИМУМ(мТабПеребор.Материал) КАК Материал,
СУММА(ВЫБОР
КОГДА мТабПеребор.КолМатериал = мТабПеребор.КолИзделие
ТОГДА 1
ИНАЧЕ 0
КОНЕЦ) КАК Корректировка
ПОМЕСТИТЬ мТабПеребор1
ИЗ
мТабПеребор КАК мТабПеребор
СГРУППИРОВАТЬ ПО
мТабПеребор.Изделие
;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
мТабПеребор11.Изделие КАК Изделие,
мТабПеребор1.Материал + мТабПеребор1.Корректировка КАК Материал
ПОМЕСТИТЬ мТабПеребор2
ИЗ
мТабПеребор1 КАК мТабПеребор1
ВНУТРЕННЕЕ СОЕДИНЕНИЕ мТабПеребор1 КАК мТабПеребор11
ПО (мТабПеребор1.Изделие = мТабПеребор11.Изделие - 1)
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ
1,
0
;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
мТабПолучатель1.Ссылка,
мТабПолучатель1.Кол,
мТаб1.Ссылка КАК Партия,
мТаб1.Аналитика1
ПОМЕСТИТЬ мТабИтого
ИЗ
мТабПеребор КАК мТабПеребор
ВНУТРЕННЕЕ СОЕДИНЕНИЕ мТабПеребор2 КАК мТабПеребор2
ПО мТабПеребор.Изделие = мТабПеребор2.Изделие
И мТабПеребор.Материал >= мТабПеребор2.Материал
ВНУТРЕННЕЕ СОЕДИНЕНИЕ мТаб1 КАК мТаб1
ПО мТабПеребор.Материал = мТаб1.НН
ВНУТРЕННЕЕ СОЕДИНЕНИЕ мТабПолучатель1 КАК мТабПолучатель1
ПО мТабПеребор.Изделие = мТабПолучатель1.НН
;
Более универсальным и классическим математическим способом распределения сущностей «многие ко многим» в запросах является метод пересечения полуинтервалов. Разберем его по шагам:
[ОстатокДо, ОстатокПосле], где ОстатокПосле — это нарастающий итог количества по текущую партию включительно, а ОстатокДо = ОстатокПосле - КоличествоПартии.[ТребуетсяДо, ТребуетсяПосле].ОстатокДо < ТребуетсяПосле И ОстатокПосле > ТребуетсяДо.
(ВЫБОР КОГДА ОстатокПосле < ТребуетсяПосле ТОГДА ОстатокПосле ИНАЧЕ ТребуетсяПосле КОНЕЦ)
-
(ВЫБОР КОГДА ОстатокДо > ТребуетсяДо ТОГДА ОстатокДо ИНАЧЕ ТребуетсяДо КОНЕЦ)
Такой подход избавляет от необходимости подбирать искусственные корректировки и одинаково стабильно работает как при дроблении одной партии на несколько позиций, так и при объединении нескольких партий в одну.
Несмотря на техническую реализуемость, чисто запросный подход несет в себе серьезные архитектурные риски:
SUM(...) OVER (ORDER BY ...). Эмуляция нарастающих итогов через самосоединение по условию <= порождает сложность $O(N^2)$. Если партий или строк пересорта станет много, нагрузка на СУБД резко возрастет, переполнится tempdb и возникнет риск зависания системы.АВТОНОМЕРЗАПИСИ() во временных таблицах может выдавать непредсказуемый порядок строк в зависимости от планов выполнения СУБД, если исходные данные не упорядочены строго по уникальным полям. Нарушение порядка приведет к сбою логики ФИФО.Для практического применения в рабочих базах рекомендуется использовать следующие проверенные варианты: