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