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

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

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

Проанализируем ситуацию, с которой столкнулся автор на форуме. Его цель — получить фактический адрес получателя из документа "Заказ на доставку".

Вот первоначальный код функции:


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

Проблема этого кода скрывается в секции ИЗ. Перечисление таблиц через запятую — Документ.ЗаказНаДоставку, Справочник.Партнеры.КонтактнаяИнформация — без явного указания связи между ними приводит к так называемому декартову произведению. Система пытается соединить каждую запись из первой таблицы с каждой записью из второй. Если у вас 100 документов и 1000 записей с контактной информацией, результат запроса будет содержать 100 * 1000 = 100 000 строк. Это и есть та "шляпа", которую автор получал в консоли.

Условие ГДЕ не решает эту проблему, так как оно применяется уже после того, как декартово произведение создано. Оно лишь отфильтрует огромный результат, но не предотвратит его создание.

Решение 1: Правильное построение запроса

Чтобы избежать декартова произведения, необходимо явно указать, как таблицы связаны между собой. Для этого используются операторы соединений: ЛЕВОЕ СОЕДИНЕНИЕ, ПРАВОЕ СОЕДИНЕНИЕ, ВНУТРЕННЕЕ СОЕДИНЕНИЕ. Условие связи указывается после ключевого слова ПО.

Однако, проанализировав задачу, мы поймем, что таблица Документ.ЗаказНаДоставку в этом запросе вообще не нужна. В функцию уже передается нужный нам объект — Получатель (контрагент или партнер). Следовательно, мы можем обращаться напрямую к его контактной информации.

Правильный запрос должен выбирать данные только из одной таблицы (в данном случае, табличной части справочника или, что более правильно для современных конфигураций, из регистра сведений КонтактнаяИнформация).

Посмотрим на исправленный и оптимизированный вариант запроса, который обращается напрямую к контактной информации контрагента (партнера).


Функция ФактическийАдресКонтрагента(Контрагент)
    Запрос = Новый Запрос;
    Запрос.УстановитьПараметр("Контрагент", Контрагент);

    Запрос.Текст = 
        "ВЫБРАТЬ ПЕРВЫЕ 1
        |    КонтактнаяИнформация.Представление
        |ИЗ
        |    Справочник.Контрагенты.КонтактнаяИнформация КАК КонтактнаяИнформация
        |ГДЕ
        |    КонтактнаяИнформация.Ссылка = &Контрагент
        |    И КонтактнаяИнформация.Вид = ЗНАЧЕНИЕ(Справочник.ВидыКонтактнойИнформации.ФактАдресКонтрагента)
        |    И КонтактнаяИнформация.Тип = ЗНАЧЕНИЕ(Перечисление.ТипыКонтактнойИнформации.Адрес)";

    Результат = Запрос.Выполнить();
    Если Результат.Пустой() Тогда
        Возврат "";
    Иначе
        Выборка = Результат.Выбрать();
        Выборка.Следующий();
        Возврат Выборка.Представление;
    КонецЕсли;
КонецФункции

Что мы здесь улучшили:

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

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


ВЫБРАТЬ ПЕРВЫЕ 1
    КонтактнаяИнформация.Представление
ИЗ
    РегистрСведений.КонтактнаяИнформация.СрезПоследних(&Дата, 
        Объект = &Контрагент 
        И Вид = ЗНАЧЕНИЕ(Справочник.ВидыКонтактнойИнформации.ФактАдресКонтрагента)) КАК КонтактнаяИнформация

Решение 2: Использование стандартных функций (Рекомендуемый способ)

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

Использование этих функций имеет несколько ключевых преимуществ:

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


Функция АдресПолучателя(Получатель)

    // Получаем значение вида контактной информации. 
    // В некоторых конфигурациях имя может отличаться, например, ФактАдресПартнера.
    ВидАдреса = Справочники.ВидыКонтактнойИнформации.ФактАдресКонтрагента;
    
    // Вызываем стандартную функцию для получения представления контактной информации.
    // Название модуля и функции может незначительно отличаться в разных конфигурациях.
    Адрес = УправлениеКонтактнойИнформацией.КонтактнаяИнформацияОбъекта(Получатель, ВидАдреса, ТекущаяДата());
    
    Возврат Адрес;

КонецФункции

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

← На главную