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

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

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

Математическая логика распределения в запросе

Прежде чем приступать к написанию кода, давайте разберем математическую модель, на которой строится распределение без использования циклов. Представьте, что у нас есть ряд ячеек с определенной вместимостью и общее количество товара, которое нужно "разлить" по этим ячейкам. Чтобы понять, сколько товара попадет в конкретную ячейку, нам нужно знать два значения:

  1. Нарастающий итог вместимости всех предыдущих ячеек (включая текущую).
  2. Нарастающий итог вместимости всех предыдущих ячеек (без учета текущей).

Формула для расчета количества в каждой строке выглядит следующим образом: Количество = Максимум(0, Минимум(ЕмкостьЯчейки, ОстатокКРаспределению - НарастающийИтогПредыдущих)).

Рассмотрим подробнее, как это работает на примере. Если нам нужно распределить 10 единиц товара по ячейкам вместимостью 4, 4 и 4 единицы:

Реализация нарастающего итога через Тэта-соединение

В языке запросов 1С нарастающий итог чаще всего реализуется с помощью так называемого "Тэта-соединения" (самосоединение таблицы по условию "больше или равно"). Проанализируем этот процесс по шагам.

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


// Шаг 1: Готовим таблицу ячеек с уникальным порядком
ВЫБРАТЬ
    Остатки.Ячейка КАК Ячейка,
    Остатки.Номенклатура КАК Номенклатура,
    Остатки.Свободно КАК Вместимость,
    АВТОНОМЕРЗАПИСИ() КАК ПорядковыйНомер
ПОМЕСТИТЬ ВТ_Ячейки
ИЗ
    РегистрНакопления.ОстаткиПоЯчейкам.Остатки КАК Остатки;

Теперь мы можем применить самосоединение для расчета нарастающего итога. Мы соединяем таблицу ВТ_Ячейки саму с собой по условию, что номер строки во второй таблице меньше или равен номеру в первой.


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

Финальным шагом мы соединяем полученные итоги с таблицей прихода и применяем нашу математическую формулу. Выясним, сколько товара попадет в каждую ячейку:


// Шаг 3: Финальное распределение
ВЫБРАТЬ
    Итоги.Номенклатура,
    Итоги.Ячейка,
    ВЫБОР
        КОГДА Приход.Количество - Итоги.НарастающийИтогПредыдущих > 0
        ТОГДА ВЫБОР
            КОГДА Приход.Количество - Итоги.НарастающийИтогПредыдущих < Итоги.Вместимость
            ТОГДА Приход.Количество - Итоги.НарастающийИтогПредыдущих
            ИНАЧЕ Итоги.Вместимость
        КОНЕЦ
        ИНАЧЕ 0
    КОНЕЦ КАК Распределено
ИЗ
    ВТ_НарастающиеИтоги КАК Итоги
ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТ_Приход КАК Приход
    ПО Итоги.Номенклатура = Приход.Номенклатура
ГДЕ
    // Отсекаем пустые распределения для оптимизации
    Приход.Количество - Итоги.НарастающийИтогПредыдущих > 0;

Программный метод: "Топорное" распределение

Как отмечалось в обсуждении на форуме, "хитрый запрос — это плохой запрос". Иногда использование сложной логики в SQL-запросе может привести к проблемам с производительностью, особенно если количество ячеек исчисляется тысячами. Проанализируем альтернативный вариант — распределение во встроенном языке 1С после получения сырых данных.

Алгоритм действий следующий: мы получаем одним запросом таблицу прихода и таблицу свободных ячеек, а затем в цикле обходим их. Это решение гораздо проще в отладке и поддержке.


// Пример программного распределения
Запрос = Новый Запрос;
Запрос.Текст = "ВЫБРАТЬ ..."; // Получаем остатки и приход
Результат = Запрос.ВыполнитьПакет();

ТаблицаПрихода = Результат[0].Выгрузить();
ТаблицаЯчеек = Результат[1].Выгрузить();

Для Каждого СтрокаПрихода Из ТаблицаПрихода Цикл
    ОстатокКРаспределению = СтрокаПрихода.Количество;
    
    // Ищем подходящие ячейки по номенклатуре
    ПараметрыОтбора = Новый Структура("Номенклатура", СтрокаПрихода.Номенклатура);
    НайденныеЯчейки = ТаблицаЯчеек.НайтиСтроки(ПараметрыОтбора);
    
    Для Каждого СтрокаЯчейки Из НайденныеЯчейки Цикл
        Если ОстатокКРаспределению <= 0 Тогда
            Прервать;
        КонецЕсли;
        
        ВзятьВЯчейку = Мин(ОстатокКРаспределению, СтрокаЯчейки.Свободно);
        
        // Регистрируем распределение
        НоваяСтрока = ТаблицаРезультатов.Добавить();
        ЗаполнитьЗначенияСвойств(НоваяСтрока, СтрокаЯчейки);
        НоваяСтрока.Количество = ВзятьВЯчейку;
        
        ОстатокКРаспределению = ОстатокКРаспределению - ВзятьВЯчейку;
        СтрокаЯчейки.Свободно = СтрокаЯчейки.Свободно - ВзятьВЯчейку;
    КонецЦикла;
КонецЦикла;

Сравнение подходов: что выбрать?

Рассмотрим сильные и слабые стороны каждого метода, чтобы сделать осознанный выбор.

Плюсы метода через запрос:

Минусы метода через запрос:

Когда лучше использовать встроенный язык:

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

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

Во-вторых, помните о детерминированности. Результат запроса должен быть одинаковым при каждом запуске. Если вы не зададите четкую сортировку (например, по ссылке ячейки или приоритету), СУБД может вернуть данные в разном порядке, и распределение "поплывет".

В-третьих, проанализируем ситуацию с нулевыми остатками. Всегда добавляйте условие ГДЕ Вместимость > 0, чтобы не загромождать расчет пустыми ячейками, которые все равно не примут товар.

В современных версиях платформы (начиная с 8.3.20+) при работе с внешними СУБД можно использовать оконные функции, но для внутренних таблиц 1С этот функционал пока ограничен. Поэтому классическое самосоединение остается основным инструментом в арсенале разработчика для решения подобных задач внутри запроса.

← На главную