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