В процессе разработки в среде 1С:Предприятие 8.3 программисты часто сталкиваются с задачей получения актуальных или последних данных из справочников. Классический пример — необходимость выбрать по каждому контрагенту его последний договор. Ситуация усложняется, когда в конфигурации отсутствует периодический регистр сведений, хранящий историю договоров, и все данные приходится извлекать напрямую из справочника Справочники.ДоговорыКонтрагентов, ориентируясь исключительно на реквизит даты договора.
В этой статье мы подробно разберем, как правильно составить такой запрос, как решить проблему одинаковых дат у договоров одного контрагента, как оптимизировать производительность на больших базах данных и какие альтернативные инструменты (например, СКД) можно использовать для решения этой задачи.
Давайте проанализируем исходные данные. У нас есть справочник договоров. У каждого договора есть стандартные реквизиты: Владелец (ссылка на контрагента) и Дата (дата заключения договора). Нам необходимо получить список всех контрагентов, у каждого из которых должен быть указан ровно один договор — самый последний по дате.
Простая группировка по контрагенту с выбором максимальной даты не решит задачу напрямую, так как язык запросов 1С (как и стандартный SQL) требует, чтобы все выбираемые поля, не входящие в группировку, были агрегированы. Мы не можем просто написать ВЫБРАТЬ Владелец, Ссылка, МАКСИМУМ(Дата) и сгруппировать только по полю Владелец — платформа выдаст ошибку синтаксиса.
Для решения этой задачи мы воспользуемся классическим паттерном «Срез последних вручную». Мы разобьем задачу на два шага с помощью временных таблиц:
Владелец и Дата. Таким образом, мы отсечем все более ранние договоры и оставим только актуальные.Рассмотрим пример такого запроса:
ВЫБРАТЬ
Договоры.Владелец КАК Владелец,
МАКСИМУМ(Договоры.Дата) КАК МаксДата
ПОМЕСТИТЬ ВТ_МаксДаты
ИЗ
Справочник.ДоговорыКонтрагентов КАК Договоры
ГДЕ
НЕ Договоры.ПометкаУдаления
СГРУППИРОВАТЬ ПО
Договоры.Владелец
;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
ВТ_МаксДаты.Владелец КАК Контрагент,
Договоры.Ссылка КАК Договор,
ВТ_МаксДаты.МаксДата КАК ДатаДоговора
ИЗ
ВТ_МаксДаты КАК ВТ_МаксДаты
ВНУТРЕННЕЕ СОЕДИНЕНИЕ Справочник.ДоговорыКонтрагентов КАК Договоры
ПО ВТ_МаксДаты.Владелец = Договоры.Владелец
И ВТ_МаксДаты.МаксДата = Договоры.Дата
Этот запрос отлично работает в простых ситуациях, когда у одного контрагента не может быть двух договоров с абсолютно одинаковой датой.
Выясним причину возможного дублирования записей в результате работы предыдущего запроса. Если у одного контрагента в один и тот же день (или с абсолютно одинаковой секундой в дате) зарегистрировано два договора, наше внутреннее соединение вернет обе записи. В результате контрагент продублируется в отчете.
Чтобы избежать этой коллизии, нам необходимо применить дополнительное агрегирование. Мы можем выбрать максимальную ссылку договора для каждой максимальной даты. Давайте посмотрим, как это реализовать:
ВЫБРАТЬ
Договоры.Владелец КАК Владелец,
МАКСИМУМ(Договоры.Дата) КАК МаксДата
ПОМЕСТИТЬ ВТ_МаксДаты
ИЗ
Справочник.ДоговорыКонтрагентов КАК Договоры
СГРУППИРОВАТЬ ПО
Договоры.Владелец
;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
ВТ_МаксДаты.Владелец КАК Владелец,
МАКСИМУМ(Договоры.Ссылка) КАК ДоговорСсылка
ПОМЕСТИТЬ ВТ_ПоследниеДоговоры
ИЗ
ВТ_МаксДаты КАК ВТ_МаксДаты
ВНУТРЕННЕЕ СОЕДИНЕНИЕ Справочник.ДоговорыКонтрагентов КАК Договоры
ПО ВТ_МаксДаты.Владелец = Договоры.Владелец
И ВТ_МаксДаты.МаксДата = Договоры.Дата
СГРУППИРОВАТЬ ПО
ВТ_МаксДаты.Владелец
;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
ВТ_ПоследниеДоговоры.Владелец КАК Контрагент,
ВТ_ПоследниеДоговоры.ДоговорСсылка КАК Договор,
Договоры.Дата КАК ДатаДоговора
ИЗ
ВТ_ПоследниеДоговоры КАК ВТ_ПоследниеДоговоры
ВНУТРЕННЕЕ СОЕДИНЕНИЕ Справочник.ДоговорыКонтрагентов КАК Договоры
ПО ВТ_ПоследниеДоговоры.ДоговорСсылка = Договоры.Ссылка
Обратите внимание на использование функции МАКСИМУМ(Ссылка). Уникальные идентификаторы (GUID) в СУБД под управлением платформы 1С:Предприятие генерируются последовательно при обычном создании элементов базы данных. Это означает, что физически созданный позже договор с высокой долей вероятности будет иметь "большую" ссылку. Таким образом, конструкция МАКСИМУМ(Ссылка) вернет тот договор, который был создан в базе последним, даже если их даты совпадают секунда в секунду.
Важное предупреждение: Данная логика с последовательными GUID может нарушаться, если договоры загружались из внешних систем (например, через распределенную информационную базу (РИБ) или сторонние XML-обмены). В этих случаях GUID генерируются случайным образом. Если в вашей базе активно используются обмены, более надежным решением для разрешения коллизии одинаковых дат будет использование реквизита Код или Номер (если они заполняются строго последовательно), то есть использование конструкции МАКСИМУМ(Код).
Если в вашей базе данных зарегистрированы сотни тысяч контрагентов и миллионы договоров, соединение физической таблицы справочника с временной таблицей по полям Владелец и Дата может сильно нагрузить СУБД. Проблема кроется в отсутствии индексов по этой комбинации полей.
Давайте разберем по шагам, как оптимизировать этот запрос для достижения максимальной скорости выполнения:
Ссылка, что в СУБД происходит практически мгновенно.Посмотрим на оптимизированный пример кода запроса:
ВЫБРАТЬ
Договоры.Владелец КАК Владелец,
МАКСИМУМ(Договоры.Дата) КАК Дата
ПОМЕСТИТЬ ВТ_МаксДаты
ИЗ
Справочник.ДоговорыКонтрагентов КАК Договоры
ГДЕ
НЕ Договоры.ПометкаУдаления
СГРУППИРОВАТЬ ПО
Договоры.Владелец
ИНДЕКСИРОВАТЬ ПО
Владелец,
Дата
;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
ВТ_МаксДаты.Владелец КАК Владелец,
МАКСИМУМ(Договоры.Ссылка) КАК Ссылка
ПОМЕСТИТЬ ВТ_УникальныеСсылки
ИЗ
ВТ_МаксДаты КАК ВТ_МаксДаты
ВНУТРЕННЕЕ СОЕДИНЕНИЕ Справочник.ДоговорыКонтрагентов КАК Договоры
ПО ВТ_МаксДаты.Владелец = Договоры.Владелец
И ВТ_МаксДаты.Дата = Договоры.Дата
СГРУППИРОВАТЬ ПО
ВТ_МаксДаты.Владелец
ИНДЕКСИРОВАТЬ ПО
Ссылка
;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
ВТ_УникальныеСсылки.Владелец КАК Контрагент,
Договоры.Ссылка КАК Договор,
Договоры.Дата КАК Дата,
Договоры.Номер КАК Номер
ИЗ
ВТ_УникальныеСсылки КАК ВТ_УникальныеСсылки
ВНУТРЕННЕЕ СОЕДИНЕНИЕ Справочник.ДоговорыКонтрагентов КАК Договоры
ПО ВТ_УникальныеСсылки.Ссылка = Договоры.Ссылка
Такой подход гарантирует минимальное время выполнения запроса даже на высоконагруженных серверах 1С:Предприятия под управлением MS SQL Server или PostgreSQL.
Если вы разрабатываете отчет, то писать сложные вложенные пакетные запросы не обязательно. Платформа 1С предоставляет мощный инструмент в виде СКД, который умеет оптимизировать подобные выборки на уровне связей наборов данных.
Рассмотрим, как решить эту задачу в СКД без сложного SQL-кода:
НаборДанныхКонтрагенты), который выбирает просто список контрагентов:
ВЫБРАТЬ Ссылка КАК Контрагент ИЗ Справочник.Контрагенты
НаборДанныхДоговоры), который выбирает договоры конкретного контрагента, переданного через параметр:
ВЫБРАТЬ ПЕРВЫЕ 1 Ссылка КАК Договор, Владелец КАК Контрагент, Дата КАК Дата ИЗ Справочник.ДоговорыКонтрагентов ГДЕ Владелец = &ПараметрКонтрагент УПОРЯДОЧИТЬ ПО Дата УБЫВ, Ссылка УБЫВ
НаборДанныхКонтрагентыНаборДанныхДоговорыКонтрагент = КонтрагентПараметрКонтрагент = КонтрагентВ этом случае платформа сама сгенерирует эффективные параметризованные запросы к базе данных для каждого контрагента, выводя строго последний договор.
Попытка эмулировать срез последних по дате из справочника — это классический архитектурный антипаттерн в среде 1С. Справочники не предназначены для эффективного хранения периодической информации. Если у вас есть возможность изменять конфигурацию, правильным архитектурным решением будет создание периодического регистра сведений (например, РегистрСведений.АктуальныеДоговоры).
При записи или изменении договора в справочнике, обработчик события записывает состояние в этот регистр. В результате, для получения последнего договора по всем контрагентам вам будет достаточно выполнить простейший и максимально быстрый запрос к виртуальной таблице среза последних:
ВЫБРАТЬ
АктуальныеДоговорыСрезПоследних.Контрагент КАК Контрагент,
АктуальныеДоговорыСрезПоследних.Договор КАК Договор
ИЗ
РегистрСведений.АктуальныеДоговоры.СрезПоследних(&Период, ) КАК АктуальныеДоговорыСрезПоследних
Этот подход не только в разы ускорит получение данных, но и сделает код вашей системы чистым, понятным и масштабируемым.