При решении задач, связанных с анализом данных в 1С или любой другой СУБД, часто возникает потребность выбрать определенный набор записей, основываясь не просто на условиях для каждой строки, а на агрегированных значениях, например, на накопленной сумме. Задача, когда нам необходимо выбрать позиции номенклатуры таким образом, чтобы их общая сумма не превышала заданный лимит, является одной из таких.
Рассмотрим подробнее, как мы можем подойти к решению этой проблемы, используя возможности запросов.
Прежде чем погружаться в технические детали, давайте убедимся, что мы правильно понимаем суть задачи. У нас есть таблица с номенклатурой и суммами по каждой позиции. Нам нужно выбрать часть этих позиций так, чтобы, если мы просуммируем выбранные позиции, их общая сумма не превысила определенный порог. Важным моментом является то, что порядок выборки может влиять на итоговый набор. Например, если мы выбираем сначала самые дешевые позиции, мы сможем включить их больше, чем если бы мы начали с самых дорогих.
Также важно прояснить условие "не превышает определенный лимит по сумме например >=1000". В контексте "не превышает" обычно подразумевается "меньше или равно" (<=). Предположим, что нам нужно выбрать позиции, чья накопленная сумма меньше или равна заданному лимиту (например, 1000).
Один из наиболее эффективных и гибких способов решения этой задачи — использование нарастающих итогов (running totals) в запросе. Этот метод позволяет нам последовательно суммировать значения, пока не будет достигнут или превышен заданный лимит. Фактически, мы имитируем процесс выбора товаров один за другим, добавляя их стоимость к общей сумме.
Разберем по шагам, как это работает:
Номенклатура и подсчитать общую сумму для каждой уникальной номенклатуры. Это позволит нам работать с суммарной стоимостью каждой позиции.Этот подход часто ассоциируется с упрощенной версией "задачи о рюкзаке" (Knapsack problem). В классической задаче о рюкзаке ищут оптимальное подмножество предметов с максимальной ценностью при ограничении по весу. Наша задача проще: мы не ищем "оптимальное" подмножество (например, с максимальной суммарной стоимостью), а просто выбираем предметы в определенном порядке, пока не достигнем лимита.
Оконные функции — это мощный инструмент в SQL, который позволяет выполнять вычисления над набором строк, связанных с текущей строкой. Они идеально подходят для расчета нарастающих итогов. В 1С, начиная с платформы 8.3.16, также появилась поддержка оконных функций, что делает этот подход доступным и в запросах 1С.
Рассмотрим пример SQL-кода, который демонстрирует этот подход:
WITH АгрегированныеНоменклатуры AS (
SELECT
Номенклатура,
СУММ(Сумма) AS ОбщаяСуммаПоНоменклатуре
FROM
ВашаТаблица
GROUP BY
Номенклатура
),
НарастающийИтогПоНоменклатуре AS (
SELECT
Номенклатура,
ОбщаяСуммаПоНоменклатуре,
СУММ(ОбщаяСуммаПоНоменклатуре) OVER (ORDER BY ОбщаяСуммаПоНоменклатуре ASC) AS НакопительнаяСумма
FROM
АгрегированныеНоменклатуры
)
SELECT
Номенклатура,
ОбщаяСуммаПоНоменклатуре
FROM
НарастающийИтогПоНоменклатуре
WHERE
НакопительнаяСумма <= &ЛимитСуммы; -- Например, <= 1000
Разберем этот запрос по частям:
WITH АгрегированныеНоменклатуры AS (...)
На этом шаге мы создаем временное общее табличное выражение (CTE), которое агрегирует суммы по каждой уникальной номенклатуре. Это важно, если в исходной таблице одна и та же номенклатура может встречаться несколько раз с разными суммами.
SELECT
Номенклатура,
СУММ(Сумма) AS ОбщаяСуммаПоНоменклатуре
FROM
ВашаТаблица
GROUP BY
Номенклатура
Здесь мы группируем строки по полю Номенклатура и вычисляем общую сумму для каждой номенклатурной позиции, присваивая ей псевдоним ОбщаяСуммаПоНоменклатуре.
WITH НарастающийИтогПоНоменклатуре AS (...)
Следующее CTE использует результат первого для вычисления нарастающего итога. Здесь вступает в игру оконная функция.
SELECT
Номенклатура,
ОбщаяСуммаПоНоменклатуре,
СУММ(ОбщаяСуммаПоНоменклатуре) OVER (ORDER BY ОбщаяСуммаПоНоменклатуре ASC) AS НакопительнаяСумма
FROM
АгрегированныеНоменклатуры
Ключевым элементом здесь является СУММ(ОбщаяСуммаПоНоменклатуре) OVER (ORDER BY ОбщаяСуммаПоНоменклатуре ASC):
СУММ(ОбщаяСуммаПоНоменклатуре): Указывает, что мы хотим суммировать поле ОбщаяСуммаПоНоменклатуре.OVER (...): Определяет "окно" или набор строк, над которым будет выполняться агрегация.ORDER BY ОбщаяСуммаПоНоменклатуре ASC: Внутри предложения OVER мы указываем порядок сортировки. В данном случае, мы сортируем по возрастанию общей суммы по номенклатуре. Это означает, что нарастающий итог будет считаться, начиная с самых "дешевых" позиций. Если бы мы указали DESC, то начали бы с самых "дорогих".AS НакопительнаяСумма: Присваиваем псевдоним результату оконной функции.И, наконец, внешний запрос выбирает нужные нам поля из CTE НарастающийИтогПоНоменклатуре и применяет условие фильтрации:
SELECT
Номенклатура,
ОбщаяСуммаПоНоменклатуре
FROM
НарастающийИтогПоНоменклатуре
WHERE
НакопительнаяСумма <= &ЛимитСуммы; -- Например, <= 1000
Здесь мы отбираем только те строки, для которых вычисленная НакопительнаяСумма не превышает заданный нами &ЛимитСуммы.
Этот метод является наиболее рекомендуемым для современных СУБД, включая 1С, так как он обычно более производителен и лаконичен по сравнению с альтернативными подходами, такими как рекурсивные CTE или подзапросы, особенно для больших объемов данных.
В некоторых случаях, когда оконные функции недоступны или требуется более сложная логика, можно использовать рекурсивные CTE. Этот метод позволяет последовательно выбирать строки и накапливать сумму. Однако он является более сложным в реализации и может быть менее производительным для больших наборов данных. Рекурсивные CTE позволяют строить "цепочки" данных, что может быть полезно для нарастающих итогов, но для нашей конкретной задачи, как правило, оконные функции предпочтительнее из-за их оптимизации для таких видов вычислений.
На таких ресурсах, как Инфостарт, можно найти различные оптимизированные методы расчета нарастающих итогов, в том числе так называемый "Баттерфляй - метод быстрого расчета нарастающего итога в запросе" Ильдаровича. Эти методы часто используют комбинации подзапросов и особых приемов для достижения хорошей производительности в условиях, когда прямые оконные функции недоступны или требуют обхода специфических особенностей платформы. Для изучения этих методов мы рекомендуем обращаться к специализированным статьям и публикациям.
ORDER BY внутри OVER является ключевым. Если вам нужно выбрать сначала самые дешевые товары, используйте ASC. Если самые дорогие — DESC.Номенклатура) и сортировка (Сумма или ОбщаяСуммаПоНоменклатуре), проиндексированы. Это значительно ускорит выполнение запроса.<= (меньше или равно).Таким образом, задача выборки номенклатуры по лимиту накопленной суммы элегантно и эффективно решается с помощью оконных функций, которые позволяют нам строить нарастающие итоги прямо в теле запроса, делая код чище и производительнее.