В практике разработки на платформе 1С часто возникает задача анализа истории операций. Одной из таких задач является поиск предпоследней продажи. Это необходимо для расчета периодичности покупок, анализа динамики цен или определения интервала между визитами клиента. В этой статье мы подробно разберем несколько способов реализации этой задачи в языке запросов 1С, начиная от классических методов для старых версий платформы и заканчивая современными инструментами.
Это классический и наиболее универсальный метод, который будет работать на любой версии платформы 8.3. Его логика заключается в двух шагах: сначала мы находим самую последнюю (максимальную) дату продажи, а затем ищем максимальную дату среди всех остальных записей, исключая уже найденную последнюю.
Рассмотрим по шагам, как это реализовать. Предположим, мы работаем с регистром ВыручкаИСебестоимостьПродаж.
Шаг 1. Получение последней даты продажи
Сначала мы создаем временную таблицу, куда помещаем номенклатуру и дату ее самой последней продажи за выбранный период.
ВЫБРАТЬ
Продажи.АналитикаУчетаНоменклатуры.Номенклатура КАК Номенклатура,
МАКСИМУМ(Продажи.Период) КАК ДатаПоследнейПродажи
ПОМЕСТИТЬ ВТ_ПоследняяПродажа
ИЗ
РегистрНакопления.ВыручкаИСебестоимостьПродаж КАК Продажи
ГДЕ
Продажи.Период МЕЖДУ &НачалоПериода И &КонецПериода
СГРУППИРОВАТЬ ПО
Продажи.АналитикаУчетаНоменклатуры.Номенклатура
;
Шаг 2. Получение предпоследней даты
Теперь мы соединяем нашу временную таблицу с основной таблицей регистра еще раз. Условие соединения будет таким: дата в основной таблице должна быть строго меньше даты из временной таблицы. Таким образом, мы отсекаем самый последний факт продажи и снова ищем максимум.
ВЫБРАТЬ
ВТ_ПоследняяПродажа.Номенклатура КАК Номенклатура,
МАКСИМУМ(Продажи.Период) КАК ДатаПредпоследнейПродажи
ПОМЕСТИТЬ ВТ_ПредпоследняяПродажа
ИЗ
ВТ_ПоследняяПродажа КАК ВТ_ПоследняяПродажа
ВНУТРЕННЕЕ СОЕДИНЕНИЕ РегистрНакопления.ВыручкаИСебестоимостьПродаж КАК Продажи
ПО ВТ_ПоследняяПродажа.Номенклатура = Продажи.АналитикаУчетаНоменклатуры.Номенклатура
И ВТ_ПоследняяПродажа.ДатаПоследнейПродажи > Продажи.Период
СГРУППИРОВАТЬ ПО
ВТ_ПоследняяПродажа.Номенклатура
;
Анализируем результат: В таблице ВТ_ПредпоследняяПродажа теперь хранятся данные о вторых по дате операциях. Важно отметить, что если по товару была всего одна продажа, он не попадет в итоговую таблицу, так как условие ДатаПоследнейПродажи > Продажи.Период не выполнится.
Если ваша база работает на современной версии платформы, мы можем использовать гораздо более элегантный способ — оконные функции. Функция ROW_NUMBER() (в 1С это ПОРЯДКОВЫЙ_НОМЕР()) позволяет пронумеровать строки внутри группы прямо в запросе.
Разберем, как пронумеровать продажи для каждой номенклатуры в порядке убывания даты:
ВЫБРАТЬ
Продажи.АналитикаУчетаНоменклатуры.Номенклатура КАК Номенклатура,
Продажи.Период КАК Период,
ПОРЯДКОВЫЙ_НОМЕР() ПО (РАЗДЕЛИТЬ ПО Продажи.АналитикаУчетаНоменклатуры.Номенклатура УПОРЯДОЧИТЬ ПО Продажи.Период УБЫВ) КАК НомерСтроки
ПОМЕСТИТЬ ВТ_ПронумерованныеПродажи
ИЗ
РегистрНакопления.ВыручкаИСебестоимостьПродаж КАК Продажи
;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
ВТ_ПронумерованныеПродажи.Номенклатура,
ВТ_ПронумерованныеПродажи.Период КАК ДатаПредпоследнейПродажи
ИЗ
ВТ_ПронумерованныеПродажи КАК ВТ_ПронумерованныеПродажи
ГДЕ
ВТ_ПронумерованныеПродажи.НомерСтроки = 2
Этот метод значительно быстрее в написании и легче читается. Мы просто говорим системе: "Разбей все продажи по товарам, внутри каждой группы отсортируй их по дате от новых к старым и дай каждой номер. А потом отдай мне те, у которых номер 2".
Проанализируем критически важный нюанс. В реальных базах данных 1С (особенно в рознице) может случиться так, что в одну и ту же секунду по одному и тому же товару прошло несколько документов. Если мы будем использовать только поле Период, то максимум для обеих продаж будет одинаковым.
Чтобы избежать этой ошибки, вместо Период рекомендуется использовать МоментВремени. Момент времени включает в себя не только время до секунды, но и ссылку на документ-регистратор, что гарантирует уникальность и правильную последовательность в рамках одной секунды.
Рассмотрим пример уточнения запроса из первого метода с использованием МоментаВремени:
ВЫБРАТЬ
Продажи.АналитикаУчетаНоменклатуры.Номенклатура КАК Номенклатура,
МАКСИМУМ(Продажи.МоментВремени) КАК МаксМомент
ПОМЕСТИТЬ ВТ_МаксМомент
ИЗ
РегистрНакопления.ВыручкаИСебестоимостьПродаж КАК Продажи
СГРУППИРОВАТЬ ПО
Продажи.АналитикаУчетаНоменклатуры.Номенклатура
;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
ВТ_МаксМомент.Номенклатура,
МАКСИМУМ(Продажи.Период) КАК ПредпоследняяДата
ИЗ
ВТ_МаксМомент КАК ВТ_МаксМомент
ВНУТРЕННЕЕ СОЕДИНЕНИЕ РегистрНакопления.ВыручкаИСебестоимостьПродаж КАК Продажи
ПО ВТ_МаксМомент.Номенклатура = Продажи.АналитикаУчетаНоменклатуры.Номенклатура
И ВТ_МаксМомент.МаксМомент > Продажи.МоментВремени
СГРУППИРОВАТЬ ПО
ВТ_МаксМомент.Номенклатура
Если задача стоит получить предпоследнюю продажу только для одного конкретного товара, можно использовать более простую конструкцию с ВЫБРАТЬ ПЕРВЫЕ.
Выясним причину популярности этого метода: он не требует группировок по всей таблице. Мы просто выбираем две последние записи, сортируем их так, чтобы предпоследняя оказалась в конце, и берем ее. Однако в рамках одного запроса 1С для списка товаров такой подход реализовать сложнее без вложенных запросов.
Работа с крупными регистрами, такими как ВыручкаИСебестоимостьПродаж, может замедлять систему. Чтобы ваш запрос работал быстро, придерживайтесь следующих правил:
Номенклатура (или АналитикаУчетаНоменклатуры). Это значительно ускорит последующие соединения.Обороты с периодичностью Авто или Регистратор.ЕСТЬNULL(ДатаПредпоследнейПродажи, ДАТАВРЕМЯ(1,1,1)), чтобы корректно обрабатывать товары, у которых была всего одна продажа.Таким образом, выбор метода зависит от версии вашей платформы и объема данных. Для новых конфигураций оптимальным выбором будут оконные функции, а для универсальных решений — двойной поиск максимума через временные таблицы с использованием МоментаВремени.