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