В практике разработчика 1С часто возникает задача: отобрать документы в хронологическом порядке до тех пор, пока их суммарный итог не достигнет определенного значения. Например, нам нужно получить список первых заказов покупателя, которые в сумме дают 100 000 рублей. Если девять заказов дают 99 999 рублей, а десятый заказ — еще 5 000 рублей, то в выборку должны попасть все десять документов, так как именно на десятом заказе условие «не меньше 100 000» было выполнено.
Рассмотрим подробнее, как реализовать такую логику, разберем различные подходы от «чистого» SQL-подобного запроса до использования механизмов системы компоновки данных (СКД) и программной обработки.
Самый прямолинейный способ получить нарастающий итог исключительно средствами языка запросов — это соединение временной таблицы самой с собой (Self-Join). Проанализируем этот алгоритм по шагам.
Во-первых, нам необходимо подготовить исходные данные во временной таблице. Важно, чтобы у документов был четкий порядок, поэтому мы будем использовать МоментВремени или комбинацию Дата + Ссылка.
// Шаг 1: Подготовка данных
ВЫБРАТЬ
Заказ.Ссылка КАК Заказ,
Заказ.МоментВремени КАК Момент,
Заказ.СуммаДокумента КАК Сумма
ПОМЕСТИТЬ ВТ_Заказы
ИЗ
Документ.ЗаказПокупателя КАК Заказ
ГДЕ
Заказ.Проведен
И Заказ.Контрагент = &Контрагент;
Во-вторых, выполним соединение таблицы ВТ_Заказы (назовем ее Т1) с этой же таблицей (назовем ее Т2). Условие соединения должно определять, что для каждой записи из Т1 мы подтягиваем все записи из Т2, которые стоят в очереди ПЕРЕД ней или являются ею самой.
// Шаг 2: Расчет нарастающего итога и фильтрация
ВЫБРАТЬ
Т1.Заказ КАК Заказ,
Т1.Сумма КАК СуммаЗаказа,
СУММА(Т2.Сумма) КАК НарастающийИтог
ИЗ
ВТ_Заказы КАК Т1
ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТ_Заказы КАК Т2
ПО Т1.Момент >= Т2.Момент
СГРУППИРОВАТЬ ПО
Т1.Заказ,
Т1.Сумма,
Т1.Момент
ИМЕЮЩИЕ
СУММА(Т2.Сумма) - Т1.Сумма < 100000
УПОРЯДОЧИТЬ ПО
Т1.Момент
Важный нюанс: Обратите внимание на условие в секции ИМЕЮЩИЕ. Чтобы в выборку попал именно тот заказ, который «закрывает» порог в 100 000 рублей, мы проверяем, что сумма всех предыдущих заказов (без учета текущего) еще не достигла лимита. Это позволяет включить пограничный документ в результат.
Проанализируем ситуацию с точки зрения производительности. Метод самосоединения имеет квадратичную сложность (O(n²)). Если у одного контрагента будет 1000 заказов, системе придется перебрать до 1 000 000 связей в памяти. Чтобы запрос не привел к деградации системы, следует соблюдать правила:
Момент или Дата.Если ваша задача — вывести отчет, то нарастающий итог гораздо эффективнее реализовать через встроенные функции СКД. Это работает значительно быстрее самосоединения, так как расчет происходит линейно при обходе результата.
Выясним, как это настроить:
ВычислитьВыражение.ВычислитьВыражение("Сумма(СуммаДокумента)", , , "Первая", "Текущая").После этого в настройках отчета (вкладка "Отбор") можно будет указать условие на это вычисляемое поле. СКД сама позаботится о правильном порядке расчета.
Зачастую «простыня» запроса с нарастающими итогами уступает в читаемости и скорости простому перебору выборки в коде 1С. Рассмотрим, как это сделать максимально эффективно.
Алгоритм прост: мы выбираем все заказы, отсортированные по дате, а затем в цикле суммируем значения в переменную. Как только переменная превышает 100 000, мы добавляем текущий документ в массив и прерываем цикл.
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Заказ.Ссылка КАК Ссылка,
| Заказ.СуммаДокумента КАК Сумма
|ИЗ
| Документ.ЗаказПокупателя КАК Заказ
|ГДЕ
| Заказ.Проведен И Заказ.Контрагент = &Контрагент
|УПОРЯДОЧИТЬ ПО
| Заказ.МоментВремени";
Запрос.УстановитьПараметр("Контрагент", ВыбранныйКонтрагент);
Выборка = Запрос.Выполнить().Выбрать();
НужныеЗаказы = Новый Массив;
ОбщаяСумма = 0;
Порог = 100000;
Пока Выборка.Следующий() Цикл
ОбщаяСумма = ОбщаяСумма + Выборка.Сумма;
НужныеЗаказы.Добавить(Выборка.Ссылка);
Если ОбщаяСумма >= Порог Тогда
Прервать; // Условие выполнено, дальше искать нет смысла
КонецЕсли;
КонецЦикла;
Этот способ потребляет минимум оперативной памяти и работает за линейное время O(n). Он особенно рекомендован, если логика «не меньше» является частью сложного бизнес-процесса в модуле объекта или обработке.
Проанализируем важный технический момент: в 1С несколько документов могут иметь идентичную дату вплоть до секунды. Если использовать в запросе условие ПО Т1.Дата >= Т2.Дата, то при совпадении времени у двух документов запрос выдаст непредсказуемый результат (суммы могут задвоиться или рассчитаться некорректно).
Решение: Всегда используйте МоментВремени в запросах или комбинацию Дата + Ссылка. Это гарантирует уникальность каждой точки на временной шкале и корректность работы условия >= при расчете нарастающего итога.
Мы рассмотрели три основных подхода к решению задачи. Выбор метода зависит от контекста: