Как выбрать последний договор по дате у всех контрагентов в 1С 8.3

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

В процессе разработки в среде 1С:Предприятие 8.3 программисты часто сталкиваются с задачей получения актуальных или последних данных из справочников. Классический пример — необходимость выбрать по каждому контрагенту его последний договор. Ситуация усложняется, когда в конфигурации отсутствует периодический регистр сведений, хранящий историю договоров, и все данные приходится извлекать напрямую из справочника Справочники.ДоговорыКонтрагентов, ориентируясь исключительно на реквизит даты договора.

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

Постановка задачи и анализ проблемы

Давайте проанализируем исходные данные. У нас есть справочник договоров. У каждого договора есть стандартные реквизиты: Владелец (ссылка на контрагента) и Дата (дата заключения договора). Нам необходимо получить список всех контрагентов, у каждого из которых должен быть указан ровно один договор — самый последний по дате.

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

Решение 1: Базовый запрос с использованием временных таблиц

Для решения этой задачи мы воспользуемся классическим паттерном «Срез последних вручную». Мы разобьем задачу на два шага с помощью временных таблиц:

  1. Сначала мы выберем всех контрагентов (владельцев) и для каждого из них найдем максимальную дату договора. Этот результат мы поместим во временную таблицу.
  2. Затем мы соединим полученную временную таблицу с исходной таблицей договоров по двум полям: Владелец и Дата. Таким образом, мы отсечем все более ранние договоры и оставим только актуальные.

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


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

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

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

Проблема одинаковых дат и выбор МАКСИМУМ(Ссылка)

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

Чтобы избежать этой коллизии, нам необходимо применить дополнительное агрегирование. Мы можем выбрать максимальную ссылку договора для каждой максимальной даты. Давайте посмотрим, как это реализовать:


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

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

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

Обратите внимание на использование функции МАКСИМУМ(Ссылка). Уникальные идентификаторы (GUID) в СУБД под управлением платформы 1С:Предприятие генерируются последовательно при обычном создании элементов базы данных. Это означает, что физически созданный позже договор с высокой долей вероятности будет иметь "большую" ссылку. Таким образом, конструкция МАКСИМУМ(Ссылка) вернет тот договор, который был создан в базе последним, даже если их даты совпадают секунда в секунду.

Важное предупреждение: Данная логика с последовательными GUID может нарушаться, если договоры загружались из внешних систем (например, через распределенную информационную базу (РИБ) или сторонние XML-обмены). В этих случаях GUID генерируются случайным образом. Если в вашей базе активно используются обмены, более надежным решением для разрешения коллизии одинаковых дат будет использование реквизита Код или Номер (если они заполняются строго последовательно), то есть использование конструкции МАКСИМУМ(Код).

Решение 2: Оптимизация производительности на больших базах данных

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

Давайте разберем по шагам, как оптимизировать этот запрос для достижения максимальной скорости выполнения:

  1. Индексация временных таблиц: Мы обязательно должны индексировать временные таблицы по полям связи.
  2. Разделение на три этапа: Мы сначала получаем пары контрагент-дата, индексируем их. Затем получаем связи контрагент-ссылка по индексу и тоже индексируем. На финальном этапе мы соединяем результирующую таблицу по первичному ключу Ссылка, что в СУБД происходит практически мгновенно.

Посмотрим на оптимизированный пример кода запроса:


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

ИНДЕКСИРОВАТЬ ПО
    Владелец,
    Дата
;

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

ИНДЕКСИРОВАТЬ ПО
    Ссылка
;

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

Такой подход гарантирует минимальное время выполнения запроса даже на высоконагруженных серверах 1С:Предприятия под управлением MS SQL Server или PostgreSQL.

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

Если вы разрабатываете отчет, то писать сложные вложенные пакетные запросы не обязательно. Платформа 1С предоставляет мощный инструмент в виде СКД, который умеет оптимизировать подобные выборки на уровне связей наборов данных.

Рассмотрим, как решить эту задачу в СКД без сложного SQL-кода:

  1. Создадим первый набор данных - запрос (назовем его НаборДанныхКонтрагенты), который выбирает просто список контрагентов: ВЫБРАТЬ Ссылка КАК Контрагент ИЗ Справочник.Контрагенты
  2. Создадим второй набор данных - запрос (назовем его НаборДанныхДоговоры), который выбирает договоры конкретного контрагента, переданного через параметр:
    
    ВЫБРАТЬ ПЕРВЫЕ 1 Ссылка КАК Договор, Владелец КАК Контрагент, Дата КАК Дата ИЗ Справочник.ДоговорыКонтрагентов ГДЕ Владелец = &ПараметрКонтрагент УПОРЯДОЧИТЬ ПО Дата УБЫВ, Ссылка УБЫВ
    
  3. Перейдем на вкладку Связи наборов данных и добавим новую связь:
    • Источник связи: НаборДанныхКонтрагенты
    • Приемник связи: НаборДанныхДоговоры
    • Условие связи: Контрагент = Контрагент
    • Параметры: ПараметрКонтрагент = Контрагент

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

Архитектурный аспект: Переход на регистры сведений

Попытка эмулировать срез последних по дате из справочника — это классический архитектурный антипаттерн в среде 1С. Справочники не предназначены для эффективного хранения периодической информации. Если у вас есть возможность изменять конфигурацию, правильным архитектурным решением будет создание периодического регистра сведений (например, РегистрСведений.АктуальныеДоговоры).

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


ВЫБРАТЬ
    АктуальныеДоговорыСрезПоследних.Контрагент КАК Контрагент,
    АктуальныеДоговорыСрезПоследних.Договор КАК Договор
ИЗ
    РегистрСведений.АктуальныеДоговоры.СрезПоследних(&Период, ) КАК АктуальныеДоговорыСрезПоследних

Этот подход не только в разы ускорит получение данных, но и сделает код вашей системы чистым, понятным и масштабируемым.

← На главную