В торговой практике часто возникает задача: клиент заказывает определенный метраж кабеля, а на складе он хранится в неделимых бобинах разной длины (например, по 28 и 67 метров). Нам необходимо написать запрос, который подберет такое количество бобин каждого вида, чтобы суммарная длина была максимально близка к требуемой (с минимальным перерасходом), учитывая при этом текущие остатки на складе. Простое деление «в лоб» (жадный алгоритм) здесь не работает, так как комбинация нескольких меньших бобин может оказаться точнее, чем использование больших.
Разберем, как решить эту задачу эффективно, используя возможности языка запросов 1С.
С технической точки зрения перед нами вариация «задачи о рюкзаке» или «задачи о сдаче». Основная сложность заключается в том, что в языке запросов 1С отсутствуют циклы. Мы не можем просто перебирать варианты в коде, если стоит жесткое условие: сделать всё одним запросом. Чтобы обойти это ограничение, мы применим метод генерации всех возможных комбинаций через декартово произведение временных таблиц.
Рассмотрим ситуацию: клиенту нужно 84 метра. Если мы возьмем 1 бобину по 67 метров, нам останется 17 метров. Придется добавить еще одну бобину на 28 метров. Итого: 67 + 28 = 95 метров (перерасход 11 метров). Однако, если мы возьмем 3 бобины по 28 метров, мы получим ровно 84 метра. Это идеальный вариант. Именно такую логику нам нужно реализовать в запросе.
Для перебора вариантов нам нужна таблица чисел (от 0 до максимально возможного количества бобин). В 1С это делается через объединение констант или соединение существующих таблиц. Проанализируем пример создания таблицы чисел от 0 до 9:
ВЫБРАТЬ 0 КАК Номер ПОМЕСТИТЬ втЦифры
ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ 1
ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ 2
ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ 3
ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ 4
ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ 5
ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ 6
ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ 7
ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ 8
ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ 9
;
Если нам нужно больше чисел (например, до 100), мы можем соединить эту таблицу саму с собой (втЦифры КАК Десятки и втЦифры КАК Единицы), получив таблицу втНомера.
Допустим, мы получили текущие остатки из регистра ТоварыНаСкладах.Остатки и поместили их во временную таблицу втОстатки. Теперь нам нужно подготовить две таблицы: все возможные варианты количества для бобин 28м и все варианты для бобин 67м, ограниченные складским остатком.
// Варианты для бобин 28 метров
ВЫБРАТЬ
втНомера.Номер КАК Кол28,
втНомера.Номер * 28 КАК Метраж28
ПОМЕСТИТЬ втВарианты28
ИЗ
втНомера КАК втНомера
ГДЕ
втНомера.Номер <= &Остаток28
;
// Варианты для бобин 67 метров
ВЫБРАТЬ
втНомера.Номер КАК Кол67,
втНомера.Номер * 67 КАК Метраж67
ПОМЕСТИТЬ втВарианты67
ИЗ
втНомера КАК втНомера
ГДЕ
втНомера.Номер <= &Остаток67
;
Теперь мы перемножаем эти таблицы. Каждая строка результата будет представлять собой уникальную комбинацию: "Столько-то бобин по 28 и столько-то по 67". Нам остается только отфильтровать те, где суммарный метраж больше или равен требуемому, и выбрать самый минимальный из них.
ВЫБРАТЬ ПЕРВЫЕ 1
Варианты28.Кол28 КАК Количество28,
Варианты67.Кол67 КАК Количество67,
Варианты28.Метраж28 + Варианты67.Метраж67 КАК ОбщийМетраж
ИЗ
втВарианты28 КАК Варианты28
КРОСС СОЕДИНЕНИЕ втВарианты67 КАК Варианты67
ГДЕ
(Варианты28.Метраж28 + Варианты67.Метраж67) >= &ТребуемыйМетраж
УПОРЯДОЧИТЬ ПО
ОбщийМетраж ВОЗР, // Сначала ищем минимальный перерасход
Количество28 + Количество67 ВОЗР // При равном метраже берем меньшее число бобин
В некоторых сообщениях форума предлагалось использовать математические формулы для расчета без полного перебора. Однако в 1С ВЫРАЗИТЬ(... КАК ЧИСЛО) работает по правилам бухгалтерского округления. Чтобы гарантированно округлить число вверх в запросе (например, при расчете недостающего остатка), используйте следующую конструкцию:
ВЫРАЗИТЬ((Число + 0.4999999999) КАК ЧИСЛО(15, 0))
Это превратит любое дробное значение больше нуля в следующее целое число, что критически важно при подборе целых бобин для покрытия остатка метража.
Проанализируем ситуацию, когда остатки на складе очень велики. Декартово произведение таблицы из 1000 строк на 1000 строк даст миллион записей, что может замедлить работу сервера. Чтобы этого избежать, мы можем ограничить выборку в таблицах втВарианты не только остатком, но и разумным пределом:
втНомера.Номер <= (ЦЕЛ(&ТребуемыйМетраж / 28) + 1)
Нет смысла проверять вариант из 100 бобин, если клиенту нужно всего 50 метров. Это значительно сужает область поиска и ускоряет выполнение запроса.
Мы выяснили, что наиболее надежный способ решения задачи оптимального подбора в запросе 1С — это использование вспомогательных таблиц с числами. Рассмотрим основные преимущества этого метода:
УПОРЯДОЧИТЬ ПО можно добавить дополнительные условия, например, приоритет отгрузки старых партий или бобин определенного типа.Таким образом, комбинируя генерацию последовательностей и кросс-соединения, мы получаем мощный инструмент для решения логистических задач внутри одного запроса к базе данных 1С.