В современных типовых конфигурациях 1С, таких как Управление торговлей 11, ERP 2 или Комплексная автоматизация 2, контактная информация (КИ) контрагентов может храниться с поддержкой истории изменений. Это означает, что для одного и того же вида контактной информации (например, «Фактический адрес») в системе может существовать несколько записей с разными датами начала действия. Перед разработчиком часто встает задача: как получить именно актуальный адрес на конкретную дату, особенно если нужно сформировать отчет по списку контрагентов.
В данной статье мы подробно разберем, как эффективно решить эту задачу с помощью механизмов запросов 1С, проанализируем возможные ошибки и рассмотрим альтернативные программные методы через БСП (Библиотеку стандартных подсистем).
Контактная информация в 1С:УТ 11 хранится в табличной части КонтактнаяИнформация справочника Контрагенты. Ключевым реквизитом для работы с историей является ДействуетС. Если этот реквизит заполнен, система считает, что запись актуальна, начиная с этой даты.
Рассмотрим ситуацию, когда нам необходимо получить последние адреса не для одного конкретного контрагента, а для целого списка (например, для выборки в отчет). Проблема заключается в том, что использование конструкции ПЕРВЫЕ 1 в данном случае невозможно, так как она ограничит весь результат запроса одной строкой, а нам нужна «последняя» строка для каждого контрагента в списке.
Наиболее оптимальный и правильный подход при работе со списками — использование временных таблиц для предварительного поиска максимальных дат. Разберем этот алгоритм по шагам.
Шаг 1. Получение максимальных дат действия. Сначала нам нужно определить для каждого контрагента и для конкретного вида КИ самую позднюю дату, которая меньше или равна нашей целевой дате.
Шаг 2. Соединение с основной таблицей. После того как мы нашли «контрольные точки» (пары Ссылка + Вид + МаксимальнаяДата), мы соединяем эту временную таблицу с исходной табличной частью, чтобы вытянуть само текстовое представление адреса или другие поля.
Проанализируем пример запроса, реализующего эту логику:
// Помещаем во временную таблицу ссылки и максимальные даты актуальности
ВЫБРАТЬ
КонтрагентыКИ.Ссылка КАК Ссылка,
МАКСИМУМ(КонтрагентыКИ.ДействуетС) КАК ДействуетС,
КонтрагентыКИ.Вид КАК Вид
ПОМЕСТИТЬ ВремАктуальныеДаты
ИЗ
Справочник.Контрагенты.КонтактнаяИнформация КАК КонтрагентыКИ
ГДЕ
КонтрагентыКИ.Вид = &Вид
И КонтрагентыКИ.ДействуетС <= &ДатаСреза
СГРУППИРОВАТЬ ПО
КонтрагентыКИ.Ссылка,
КонтрагентыКИ.Вид;
////////////////////////////////////////////////////////////////////////////////
// Выбираем данные, соединяясь по найденным критериям
ВЫБРАТЬ
ВремАктуальныеДаты.Ссылка КАК Контрагент,
КонтрагентыКИ.Представление КАК Адрес,
ВремАктуальныеДаты.ДействуетС КАК ДатаНачалаДействия
ИЗ
ВремАктуальныеДаты КАК ВремАктуальныеДаты
ЛЕВОЕ СОЕДИНЕНИЕ Справочник.Контрагенты.КонтактнаяИнформация КАК КонтрагентыКИ
ПО ВремАктуальныеДаты.Ссылка = КонтрагентыКИ.Ссылка
И ВремАктуальныеДаты.Вид = КонтрагентыКИ.Вид
И ВремАктуальныеДаты.ДействуетС = КонтрагентыКИ.ДействуетС
Рассмотрим ситуацию, когда в один и тот же день (в одну секунду) по ошибке или в результате переноса данных были введены две записи для одного вида КИ. В этом случае условие соединения ВремАктуальныеДаты.ДействуетС = КонтрагентыКИ.ДействуетС найдет обе записи, и в результате выполнения запроса вы получите дубли строк в отчете.
Чтобы гарантированно избежать этой проблемы, в серьезных корпоративных системах рекомендуется добавлять в группировку и поиск максимума реквизит НомерСтроки. Поскольку в рамках одной табличной части номера строк уникальны, это позволит однозначно идентифицировать последнюю внесенную запись.
Проанализируем уточненную схему поиска через номер строки:
МАКСИМУМ(ДействуетС) для каждого контрагента и вида КИ.МАКСИМУМ(НомерСтроки) для тех записей, у которых дата равна найденному максимуму.Ссылка, Вид и НомерСтроки.Если вы пишете код внутри конфигурации, где подключена Библиотека стандартных подсистем (БСП), крайне рекомендуется использовать встроенные программные интерфейсы вместо написания «чистых» запросов к табличным частям. Это делает код более устойчивым к изменениям метаданных платформы 1С.
Основным методом для этой задачи является функция КонтактнаяИнформацияОбъектов общего модуля УправлениеКонтактнойИнформацией. Рассмотрим пример её вызова:
// Подготовим список контрагентов
СписокСсылок = Новый Массив;
СписокСсылок.Добавить(СсылкаНаКонтрагента1);
СписокСсылок.Добавить(СсылкаНаКонтрагента2);
// Определим вид контактной информации (например, Юридический адрес)
ВидКИ = Справочники.ВидыКонтактнойИнформации.ЮрАдресКонтрагента;
// Получим таблицу с актуальными данными на текущую дату
ТаблицаАдресов = УправлениеКонтактнойИнформацией.КонтактнаяИнформацияОбъектов(
СписокСсылок,
ВидКИ,
ТекущаяДата(),
Истина // ТолькоАктуальные
);
// Теперь мы можем обойти результат
Для Каждого СтрокаАдреса Из ТаблицаАдресов Цикл
Сообщить("Контрагент: " + СтрокаАдреса.Объект + " - Адрес: " + СтрокаАдреса.Представление);
КонецЦикла;
Этот метод внутри себя уже содержит оптимизированный запрос, учитывает историю, корректно обрабатывает пустые значения и возвращает готовую ТаблицуЗначений.
Выясним еще одну важную деталь. Реквизит Представление — это просто текстовая строка. Однако 1С хранит адрес в структурированном виде (индекс, город, улица) в реквизите ЗначенияПолей (обычно в формате JSON или XML). Если в вашем запросе или отчете нужно вывести город отдельно от улицы, вам не поможет простое получение Представление.
В таких случаях алгоритм следующий:
ЗначенияПолей.УправлениеКонтактнойИнформацией.КонтактнаяИнформацияВФорматеJSON (или аналогичные для XML), чтобы распарсить данные.Посмотрим на ситуацию со стороны быстродействия. Если справочник Контрагенты содержит сотни тысяч записей, а история адресов ведется активно, запросы с вложенными таблицами и группировками могут работать медленно.
Для оптимизации можно применить следующий подход:
выбираем все записи контактной информации для нужных контрагентов в обычную таблицу значений, сортируем её по убыванию даты и номера строки, а затем используем метод Свернуть по колонке Ссылка (предварительно убедившись, что самая актуальная запись оказалась первой). В оперативной памяти 1С такие операции зачастую проходят быстрее, чем сложные JOIN на уровне SQL-сервера при работе с табличными частями справочников.
Для получения актуального адреса из истории в 1С:
ВЫБРАТЬ ПЕРВЫЕ 1 с сортировкой по ДействуетС УБЫВ.МАКСИМУМ(ДействуетС).НомерСтроки.УправлениеКонтактнойИнформацией.КонтактнаяИнформацияОбъектов.