Как в запросе 1С получить первое контактное лицо контрагента в УНФ

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

Приветствуем! При разработке отчетов и доработке конфигураций на платформе 1С:Предприятие 8 одна из частых задач — вывести список контрагентов и «прицепить» к каждому ровно одно контактное лицо. В конфигурациях, использующих современные архитектурные решения (таких как 1С:Управление нашей фирмой (УНФ), 1С:ERP или 1С:Комплексная автоматизация), связь между контрагентами и контактами реализована не через простое подчинение справочников, а по принципу «многие-ко-многим». Для этого применяется специальный регистр сведений.

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

Архитектурные особенности хранения контактных лиц в УНФ

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

  1. Физические лица хранятся отдельно в справочнике Контакты (или КонтактныеЛица).
  2. Связь между контрагентом и контактным лицом фиксируется в независимом регистре сведений СвязиКонтрагентКонтакт.
  3. Одно и то же контактное лицо может быть связано с несколькими контрагентами (например, внешний юрист или приходящий бухгалтер на аутсорсинге).
  4. Для определения приоритета контактов в регистре предусмотрен реквизит Порядок (числовое поле сортировки) и признак деактивации СвязьНедействительна.

Именно из-за структуры «многие-ко-многим» прямое левое соединение справочника контрагентов с регистром сведений приводит к «размножению» строк в результате запроса: если у контрагента привязано 5 контактов, в отчете мы получим 5 одинаковых строк контрагента.

Разбор типичной ошибки: конструкция ПЕРВЫЕ 1 в соединении

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


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

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

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

Правильное решение: Пакетный запрос с временными таблицами

Наиболее надежный, быстрый и соответствующий стандартам разработки 1С метод — это использование временных таблиц. Мы разобьем задачу на шаги, чтобы гарантировать максимальную скорость работы СУБД и полное отсутствие дублей.

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

  1. ВТ_СвязиАктивные: Выбираем только действующие связи из среза последних регистра сведений, отсекая помеченные как недействительные.
  2. ВТ_МинимальныйПорядок: Для каждого контрагента определяем минимальное значение в поле Порядок. Это позволит нам понять, какой контакт был добавлен первым или назначен самым приоритетным.
  3. ВТ_ИтоговыеСвязи: Соединяем активные связи с минимальным порядком. Чтобы избежать дублирования (если у двух разных контактов одного контрагента по ошибке указан одинаковый порядок, например, 0), мы дополнительно группируем результат и берем МИНИМУМ(Контакт). Это ключевой шаг для стабильности отчета!
  4. Финальная выборка: Соединяем справочник Контрагенты с подготовленной чистой временной таблицей связей.

Давайте реализуем эту схему в коде:


// ШАГ 1. Получаем срез последних и отсекаем недействующие связи
ВЫБРАТЬ
    Связи.Контрагент КАК Контрагент,
    Связи.Контакт КАК Контакт,
    Связи.Порядок КАК Порядок
ПОМЕСТИТЬ ВТ_СвязиАктивные
ИЗ
    РегистрСведений.СвязиКонтрагентКонтакт.СрезПоследних КАК Связи
ГДЕ
    НЕ Связи.СвязьНедействительна
;

////////////////////////////////////////////////////////////////////////////////
// ШАГ 2. Находим минимальный порядок связи для каждого контрагента
ВЫБРАТЬ
    АктивныеСвязи.Контрагент КАК Контрагент,
    МИНИМУМ(АктивныеСвязи.Порядок) КАК МинимальныйПорядок
ПОМЕСТИТЬ ВТ_МинимальныйПорядок
ИЗ
    ВТ_СвязиАктивные КАК АктивныеСвязи
СГРУППИРОВАТЬ ПО
    АктивныеСвязи.Контрагент
;

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

////////////////////////////////////////////////////////////////////////////////
// ШАГ 4. Соединяем справочник Контрагенты с уникальными контактами
ВЫБРАТЬ
    Контрагенты.Ссылка КАК Контрагент,
    Контрагенты.Код КАК Код,
    Контрагенты.Наименование КАК Наименование,
    ЕСТЬNULL(ИтоговыеСвязи.Контакт, ЗНАЧЕНИЕ(Справочник.Контакты.ПустаяСсылка)) КАК ПервоеКонтактноеЛицо
ИЗ
    Справочник.Контрагенты КАК Контрагенты
    ЛЕВОЕ СОЕДИНЕНИЕ ВТ_ИтоговыеСвязи КАК ИтоговыеСвязи
        ПО Контрагенты.Ссылка = ИтоговыеСвязи.Контрагент
ГДЕ
    НЕ Контрагенты.ЭтоГруппа

Проанализируем преимущества этого подхода. Использование временных таблиц позволяет оптимизатору СУБД (MS SQL Server, PostgreSQL) построить оптимальный план запроса. Индексы будут использоваться максимально эффективно, а исключение дублей на третьем шаге гарантирует, что количество строк на выходе будет в точности соответствовать количеству контрагентов в базе данных.

Альтернатива: Коррелированный (сопоставленный) подзапрос

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

Давайте посмотрим, как выглядит такая конструкция в синтаксисе 1С:


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

Важное предостережение по производительности! Несмотря на внешнюю компактность кода, этот подход обладает существенным минусом — крайне низкой скоростью работы на больших базах данных. При выполнении такого запроса СУДБ вынуждена выполнять вложенную выборку построчно (использовать алгоритм Nested Loops). Если в вашей базе данных более 10 000 контрагентов, построение такого отчета может вызвать перегрузку процессора сервера и завершиться ошибкой по таймауту выполнения.

Кроме того, в платформе 1С отсутствуют аналитические оконные функции (такие как ROW_NUMBER() OVER в классическом SQL), поэтому эмуляция выбора первой строки через корреляционные подзапросы в 1С всегда является крайней мерой, не рекомендуемой для высоконагруженных систем.

Использование готового реквизита конфигурации УНФ

Не забываем также о бизнес-логике самой конфигурации. Перед тем как писать сложные запросы к регистрам связей, проанализируем структуру справочника Контрагенты. В актуальных версиях УНФ разработчики предусмотрели стандартный реквизит ОсновноеКонтактноеЛицо непосредственно в самом справочнике.

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


ВЫБРАТЬ
    Контрагенты.Ссылка КАК Контрагент,
    Контрагенты.ОсновноеКонтактноеЛицо КАК ПервоеКонтактноеЛицо
ИЗ
    Справочник.Контрагенты КАК Контрагенты
ГДЕ
    НЕ Контрагенты.ЭтоГруппа

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

Программный метод получения контакта через БСП

Если вы решаете задачу программно (например, в модуле объекта при проведении документа или заполнении реквизитов на форме), писать ручные запросы к регистрам связей вовсе не обязательно. Конфигурация УНФ содержит механизмы Библиотеки стандартных подсистем (БСП) и специализированные модули CRM.

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


// Получаем ссылку на основное контактное лицо контрагента
КонтактноеЛицо = ПартнерыИКонтрагенты.ПолучитьКонтактноеЛицоПартнераКонтрагентаПоУмолчанию(ВыбранныйКонтрагент);

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

Подведем итоги

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

  1. Пакетный запрос с временными таблицами — оптимальный, безопасный и масштабируемый вариант для построения отчетов любой сложности. Он защищает от дублирования строк при совпадении приоритетов.
  2. Использование реквизита "ОсновноеКонтактноеЛицо" — самый быстродействующий вариант, если бизнес-логика предприятия гарантирует заполнение этого поля пользователями.
  3. Методы общего модуля БСП — идеальное решение для разработки логики во встроенном языке 1С вне контекста сложных запросов.

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

← На главную