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