В практике разработки на платформе 1С часто возникает задача, напоминающая обратный метод ФИФО: нам нужно определить, из каких последних поставок сложился текущий остаток товара или по какой цене к нам пришли последние N единиц (например, 500 коробок). Сложность заключается в том, что эти 500 коробок могли прийти как одной партией, так и десятками мелких поступлений в течение года.
В этой статье мы подробно разберем, как реализовать подобный алгоритм с помощью языка запросов 1С, обсудим классический метод нарастающих итогов, современные оконные функции и способы избежать типичных ошибок при сортировке документов.
Представим ситуацию: на складе находится товар, и нам нужно проанализировать последние поступления, чтобы их суммарное количество составило ровно 500 единиц. Нам необходимо не просто отобрать документы, а рассчитать, какая часть количества из самого «старого» (в рамках этой выборки) документа попадает в наш лимит.
Рассмотрим два основных подхода к решению этой задачи:
Проанализируем логику этого метода. Чтобы в каждой строке таблицы поступлений знать, сколько товара пришло «до неё» и «вместе с ней», мы должны соединить таблицу саму с собой. Условие соединения будет следующим: к текущей строке присоединяются все строки, которые по времени стоят «после» неё (если мы идем в обратном хронологическом порядке).
Рассмотрим по шагам, как составить такой запрос:
1. Сначала подготовим временную таблицу со всеми приходами по нужному товару. Важно сразу отобрать только актуальные документы (например, за последний год), чтобы не перегружать базу данных.
ВЫБРАТЬ
Приход.Ссылка КАК Ссылка,
Приход.Период КАК Период,
Приход.Количество КАК Количество,
Приход.Цена КАК Цена
ПОМЕСТИТЬ ВТ_ВсеПоступления
ИЗ
РегистрНакопления.Закупки.Обороты(&НачалоПериода, &КонецПериода, Регистратор, Номенклатура = &Товар) КАК Приход
;
2. Рассчитаем нарастающий итог. Мы будем соединять ВТ_ВсеПоступления (как Т1) с ней же (как Т2) по условию, что Т2.Период >= Т1.Период. Это позволит нам получить кумулятивную сумму количества.
Важный нюанс: Если два документа имеют одинаковое время, простого сравнения периодов недостаточно. Как было замечено в обсуждении, нужно использовать Момент Времени или комбинацию Дата + Ссылка для однозначного порядка.
ВЫБРАТЬ
Т1.Ссылка КАК Документ,
Т1.Количество КАК КоличествоВДокументе,
Т1.Цена КАК Цена,
СУММА(Т2.Количество) КАК НакопленныйИтог
ИЗ
ВТ_ВсеПоступления КАК Т1
ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТ_ВсеПоступления КАК Т2
ПО Т1.Период < Т2.Период
ИЛИ (Т1.Период = Т2.Период И Т1.Ссылка <= Т2.Ссылка)
СГРУППИРОВАТЬ ПО
Т1.Ссылка,
Т1.Количество,
Т1.Цена
Если ваша платформа выше версии 8.3.20, крайне рекомендуем использовать оконные функции. Это работает значительно быстрее, так как не требует декартова произведения таблиц при соединении.
Проанализируем пример использования функции СУММА() ОБЩЕЕ:
ВЫБРАТЬ
Т.Ссылка,
Т.Количество,
Т.Цена,
СУММА(Т.Количество) ОБЩЕЕ (УПОРЯДОЧИТЬ ПО Т.Период УБЫВ, Т.Ссылка УБЫВ) КАК НакопленныйИтог
ИЗ
ВТ_ВсеПоступления КАК Т
В данном случае платформа сама вычислит сумму с накоплением, двигаясь от самых свежих записей к более старым. Это наиболее эффективный способ решения задачи «ФИФО/ЛИФО в запросе».
Выясним причину, по которой простого отбора строк с НакопленныйИтог <= 500 недостаточно. Представим, что последний документ, который входит в нашу выборку, имеет количество 200 единиц, а до него мы уже накопили 450. Если мы возьмем весь документ, то получим 650 единиц вместо нужных 500. Нам нужно взять из этого документа только 50 единиц.
Для этого в итоговом запросе используем конструкцию ВЫБОР:
ВЫБРАТЬ
Запрос.Документ,
ВЫБОР
КОГДА Запрос.НакопленныйИтог - Запрос.КоличествоВДокументе >= &НужноеКолво
ТОГДА 0 -- Весь товар из этого документа уже лишний
КОГДА Запрос.НакопленныйИтог <= &НужноеКолво
ТОГДА Запрос.КоличествоВДокументе -- Документ входит целиком
ИНАЧЕ &НужноеКолво - (Запрос.НакопленныйИтог - Запрос.КоличествоВДокументе) -- Берем остаток
КОНЕЦ КАК КоличествоДляРасчета,
Запрос.Цена
ИЗ
(Здесь_Ваш_Запрос_С_Нарастающим_Итогом) КАК Запрос
ГДЕ
(Запрос.НакопленныйИтог - Запрос.КоличествоВДокументе) < &НужноеКолво
Разберем ситуацию, упомянутую в сообщениях форума: что делать, если два поступления созданы в одну и ту же секунду? Как мы уже выяснили, использование только даты приведет к тому, что нарастающий итог для обоих документов будет одинаковым, и алгоритм «сломается».
Решение: Всегда добавляйте ссылку в условия сравнения и сортировки. В 1С ссылка является гарантированно уникальным идентификатором. Сравнение Т1.Ссылка < Т2.Ссылка в запросе работает корректно и позволяет выстроить документы в детерминированную очередь.
Проанализируем, как сделать запрос максимально производительным:
Период и Ссылка. Это ускорит операцию соединения в разы.Таким образом, комбинируя расчет нарастающего итога с математическим вычислением остатка в граничной строке, мы получаем точный инструмент для определения стоимости последних поступивших товаров. Выбор между левым соединением и оконными функциями должен зависеть от версии платформы и требований к производительности.