Как в запросе 1С выбрать последний документ по каждому договору?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

При построении отчетов по справочникам (например, по договорам контрагентов) часто возникает задача вывести реквизиты последнего связанного документа — к примеру, дату или номер крайнего счета на оплату. Начинающие разработчики часто пытаются использовать конструкцию ВЫБРАТЬ ПЕРВЫЕ 1 ... УПОРЯДОЧИТЬ ПО ... УБЫВ во вложенном запросе, однако сталкиваются с ограничениями языка запросов 1С.

Разберем по шагам, почему привычная конструкция с ПЕРВЫЕ 1 не работает построчно в связях, а также рассмотрим правильные и производительные способы решения этой задачи.

Ограничения языка запросов 1С при получении первых записей

Выясним причину, по которой нельзя просто вставить вложенный запрос с ограничением первых строк в секцию ЛЕВОЕ СОЕДИНЕНИЕ:

  1. Отсутствие коррелированных подзапросов в соединениях: В платформе 1С не поддерживаются конструкции вроде CROSS APPLY или OUTER APPLY (доступные в ряде СУБД). Конструкция ВЫБРАТЬ ПЕРВЫЕ 1 обрезает результат всего подзапроса целиком до одной строки, а не выбирает одну строку для каждой группы или строки внешнего запроса.
  2. Отсутствие оконных функций: В языке запросов 1С нельзя применить функции нумерации строк вида ROW_NUMBER() OVER (PARTITION BY ... ORDER BY ...).

Именно поэтому для получения последней даты или ссылки на крайний документ применяются агрегатные функции (МАКСИМУМ), группировки и временные таблицы.

Способ 1. Агрегация максимальной даты через группировку

Если в отчет требуется вывести только дату последнего документа по договору (без необходимости получать ссылку на конкретный счет), простейшее решение — применить агрегатную функцию МАКСИМУМ и сгруппировать результат по всем неагрегированным полям.

Посмотрим на пример такого запроса:


ВЫБРАТЬ
	ДоговорыКонтрагентов.Ссылка КАК Ссылка,
	ДоговорыКонтрагентов.Представление КАК Представление,
	ДоговорыКонтрагентов.Владелец КАК Владелец,
	ПРЕДСТАВЛЕНИЕ(ДоговорыКонтрагентов.Владелец) КАК ВладелецПредставление,
	ДоговорыКонтрагентов.Дата КАК ДатаДоговора,
	ДоговорыКонтрагентов.Номер КАК НомерДоговора,
	ЗначенияСвойствОбъектов.Значение КАК Периодичность,
	ПРЕДСТАВЛЕНИЕ(ЗначенияСвойствОбъектов.Значение) КАК ПериодичностьПредставление,
	МАКСИМУМ(СчетНаОплатуПокупателю.Дата) КАК ДатаПоследнегоСчета
ИЗ
	Справочник.ДоговорыКонтрагентов КАК ДоговорыКонтрагентов
	ЛЕВОЕ СОЕДИНЕНИЕ Документ.СчетНаОплатуПокупателю КАК СчетНаОплатуПокупателю
		ПО ДоговорыКонтрагентов.Ссылка = СчетНаОплатуПокупателю.ДоговорКонтрагента
	ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ЗначенияСвойствОбъектов КАК ЗначенияСвойствОбъектов
		ПО ДоговорыКонтрагентов.Ссылка = ЗначенияСвойствОбъектов.Объект
		И (ЗначенияСвойствОбъектов.Свойство = &СвойствоПериодичность)
ГДЕ
	ДоговорыКонтрагентов.Наименование ПОДОБНО "%ЦТО%"
СГРУППИРОВАТЬ ПО
	ДоговорыКонтрагентов.Ссылка,
	ДоговорыКонтрагентов.Представление,
	ДоговорыКонтрагентов.Владелец,
	ПРЕДСТАВЛЕНИЕ(ДоговорыКонтрагентов.Владелец),
	ДоговорыКонтрагентов.Дата,
	ДоговорыКонтрагентов.Номер,
	ЗначенияСвойствОбъектов.Значение,
	ПРЕДСТАВЛЕНИЕ(ЗначенияСвойствОбъектов.Значение)

Способ 2. Выборка последних дат через временную таблицу

Когда запрос становится сложным, группировка большого количества полей в основном теле запроса может снижать читаемость и производительность. В таких случаях эффективнее собрать максимальные даты по договорам в отдельную временную таблицу, а затем соединить ее с основной выборкой.

Разберем по шагам построение пакета запросов:

  1. В первом запросе выбираем документы, группируем их по договорам и вычисляем МАКСИМУМ(Дата), сохраняя результат во временную таблицу ВТ_ПоследниеСчета.
  2. Во втором запросе выбираем справочник договоров и соединяем его левым соединением с созданной временной таблицей.

Рассмотрим пример реализации:


ВЫБРАТЬ
	СчетНаОплатуПокупателю.ДоговорКонтрагента КАК ДоговорКонтрагента,
	МАКСИМУМ(СчетНаОплатуПокупателю.Дата) КАК ДатаПоследнегоСчета
ПОМЕСТИТЬ ВТ_ПоследниеСчета
ИЗ
	Документ.СчетНаОплатуПокупателю КАК СчетНаОплатуПокупателю
ГДЕ
	СчетНаОплатуПокупателю.Проведен = ИСТИНА
СГРУППИРОВАТЬ ПО
	СчетНаОплатуПокупателю.ДоговорКонтрагента

ИНДЕКСИРОВАТЬ ПО
	ДоговорКонтрагента
;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
	ДоговорыКонтрагентов.Ссылка КАК Ссылка,
	ДоговорыКонтрагентов.Представление КАК Представление,
	ДоговорыКонтрагентов.Владелец КАК Владелец,
	ПРЕДСТАВЛЕНИЕ(ДоговорыКонтрагентов.Владелец) КАК ВладелецПредставление,
	ДоговорыКонтрагентов.Дата КАК ДатаДоговора,
	ДоговорыКонтрагентов.Номер КАК НомерДоговора,
	ЗначенияСвойствОбъектов.Значение КАК Периодичность,
	ПРЕДСТАВЛЕНИЕ(ЗначенияСвойствОбъектов.Значение) КАК ПериодичностьПредставление,
	ВТ_ПоследниеСчета.ДатаПоследнегоСчета КАК ДатаПоследнегоСчета
ИЗ
	Справочник.ДоговорыКонтрагентов КАК ДоговорыКонтрагентов
	ЛЕВОЕ СОЕДИНЕНИЕ ВТ_ПоследниеСчета КАК ВТ_ПоследниеСчета
		ПО ДоговорыКонтрагентов.Ссылка = ВТ_ПоследниеСчета.ДоговорКонтрагента
	ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ЗначенияСвойствОбъектов КАК ЗначенияСвойствОбъектов
		ПО ДоговорыКонтрагентов.Ссылка = ЗначенияСвойствОбъектов.Объект
		И (ЗначенияСвойствОбъектов.Свойство = &СвойствоПериодичность)
ГДЕ
	ДоговорыКонтрагентов.Наименование ПОДОБНО "%ЦТО%"

Способ 3. Получение ссылки на последний документ при совпадении дат

Если в отчете требуется вывести не только дату, но и сам документ (ссылку или номер), простого соединения по дате недостаточно: если в одну секунду или день создано несколько документов по одному договору, произойдет задвоение строк.

Для гарантированного получения ровно одного последнего документа сформируем каскад временных таблиц:

  1. Вычисляем максимальную дату документа по договору.
  2. Соединяем результат с документами и вычисляем МАКСИМУМ(Ссылка) среди документов с этой датой (это разрешает коллизии дублей).
  3. Соединяем итоговую таблицу ссылок со справочником договоров.

ВЫБРАТЬ
	Счет.ДоговорКонтрагента КАК ДоговорКонтрагента,
	МАКСИМУМ(Счет.Дата) КАК МаксДата
ПОМЕСТИТЬ ВТ_МаксДаты
ИЗ
	Документ.СчетНаОплатуПокупателю КАК Счет
СГРУППИРОВАТЬ ПО
	Счет.ДоговорКонтрагента
;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
	Счет.ДоговорКонтрагента КАК ДоговорКонтрагента,
	МАКСИМУМ(Счет.Ссылка) КАК ПоследнийСчет
ПОМЕСТИТЬ ВТ_ПоследниеДокументы
ИЗ
	Документ.СчетНаОплатуПокупателю КАК Счет
	ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТ_МаксДаты КАК ВТ_МаксДаты
		ПО Счет.ДоговорКонтрагента = ВТ_МаксДаты.ДоговорКонтрагента
		И Счет.Дата = ВТ_МаксДаты.МаксДата
СГРУППИРОВАТЬ ПО
	Счет.ДоговорКонтрагента

ИНДЕКСИРОВАТЬ ПО
	ДоговорКонтрагента
;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
	Договоры.Ссылка КАК Договор,
	ВТ_ПоследниеДокументы.ПоследнийСчет КАК ПоследнийСчет,
	ВТ_ПоследниеДокументы.ПоследнийСчет.Дата КАК ДатаСчета,
	ВТ_ПоследниеДокументы.ПоследнийСчет.Номер КАК НомерСчета
ИЗ
	Справочник.ДоговорыКонтрагентов КАК Договоры
	ЛЕВОЕ СОЕДИНЕНИЕ ВТ_ПоследниеДокументы КАК ВТ_ПоследниеДокументы
		ПО Договоры.Ссылка = ВТ_ПоследниеДокументы.ДоговорКонтрагента

Способ 4. Реализация через Систему компоновки данных (СКД)

Если отчет строится на базе СКД, задачу можно решить штатными механизмами компоновки без ручного усложнения текста запроса:

  1. Через связь наборов данных: Создаем два набора данных — набор-источник (справочник договоров) и набор-приемник (документы). Связываем их по полю Ссылка = ДоговорКонтрагента.
  2. Через ресурсы СКД: Добавляем поле ДатаСчета в список ресурсов отчета и задаем для него выражение Максимум(ДатаСчета). При настройке структуры с группировкой по договору СКД автоматически свернет подчиненные записи до одного максимального значения.

Важные рекомендации по оптимизации запросов

Проанализируем распространенные ошибки, которые могут замедлить выполнение запроса:

  1. Порядок таблиц в соединениях: При формировании отчетов по договорам ведущей таблицей должен выступать справочник ДоговорыКонтрагентов, а документы должны присоединяться к нему через ЛЕВОЕ СОЕДИНЕНИЕ. Если ведущей таблицей сделать документы, то договоры без выписанных счетов просто не попадут в отчет.
  2. Разыменование ссылок через точку в условиях: Конструкция ЗначенияСвойствОбъектов.Свойство.Наименование = "Периодичность (мес)" приводит к лишнему неявному соединению и строковому сравнению. Правильно передавать ссылку на свойство через параметр: ЗначенияСвойствОбъектов.Свойство = &СвойствоПериодичность.
  3. Индексация реквизитов: Для быстрой группировки и соединений убедитесь, что реквизит ДоговорКонтрагента в документе проиндексирован.
← На главную