Как правильно соединить таблицы в запросе 1С и избежать дублирования строк при наличии нескольких записей в правой таблице

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

При разработке отчетов и обработок в среде 1С:Предприятие одной из самых распространенных задач является получение данных из нескольких связанных таблиц. Часто возникает ситуация, когда к основной таблице (например, справочнику Контрагенты) необходимо присоединить данные из подчиненной таблицы (например, регистра сведений КонтактнаяИнформация). Если у одного контрагента заполнено несколько видов контактной информации (телефон, юридический адрес, электронная почта), обычное ЛЕВОЕ СОЕДИНЕНИЕ приводит к "раздуванию" выборки: одна строка контрагента превращается в несколько строк по количеству найденных записей в регистре.

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

Причина возникновения дублей при соединении

Разберем ситуацию на примере. Допустим, мы хотим получить список контрагентов с их юридическими адресами и телефонами. Если мы просто напишем запрос с соединением по полю Объект, система сопоставит каждую запись из справочника Контрагенты с каждой подходящей записью из регистра КонтактнаяИнформация. Если у контрагента "Альфа" есть и адрес, и телефон, в результате запроса для "Альфы" появятся две строки. Это происходит потому, что SQL-механизм, лежащий в основе запросов 1С, создает декартово произведение строк, удовлетворяющих условию связи.

Чтобы получить результат, где один контрагент — это одна строка с заполненными колонками "Адрес" и "Телефон", нам нужно применить специальные приемы компоновки запроса.

Способ 1. Использование нескольких левых соединений с условием

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

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


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

Важный нюанс: условие ЮрАдрес.Вид = ... обязательно должно находиться внутри блока ПО. Если вы вынесете его в блок ГДЕ, левое соединение фактически превратится во внутреннее, и контрагенты без адреса просто исчезнут из выборки.

Способ 2. Группировка и агрегатные функции (Метод Pivot)

Если видов контактной информации много, плодить десятки соединений неэффективно с точки зрения производительности. Более профессиональный подход — прочитать таблицу контактной информации один раз, использовать конструкцию ВЫБОР и затем "схлопнуть" результат с помощью группировки.

Проанализируем логику этого метода:

  1. Выбираем ссылку контрагента.
  2. Для колонки "Адрес" пишем условие: если это вид "Адрес", то берем представление, иначе — пустую строку.
  3. Для колонки "Телефон" аналогично: если вид "Телефон", берем представление.
  4. Применяем агрегатную функцию МАКСИМУМ к этим полям и группируем по ссылке.

Посмотрим на пример реализации:


ВЫБРАТЬ
    Контрагенты.Ссылка КАК Ссылка,
    МАКСИМУМ(ВЫБОР
            КОГДА КонтактнаяИнформация.Вид = ЗНАЧЕНИЕ(Справочник.ВидыКонтактнойИнформации.ЮрАдресКонтрагента)
                ТОГДА КонтактнаяИнформация.Представление
            ИНАЧЕ ""
        КОНЕЦ) КАК ЮрАдрес,
    МАКСИМУМ(ВЫБОР
            КОГДА КонтактнаяИнформация.Вид = ЗНАЧЕНИЕ(Справочник.ВидыКонтактнойИнформации.ТелефонКонтрагента)
                ТОГДА КонтактнаяИнформация.Представление
            ИНАЧЕ ""
        КОНЕЦ) КАК Телефон
ИЗ
    Справочник.Контрагенты КАК Контрагенты
        ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.КонтактнаяИнформация КАК КонтактнаяИнформация
        ПО Контрагенты.Ссылка = КонтактнаяИнформация.Объект
СГРУППИРОВАТЬ ПО
    Контрагенты.Ссылка

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

Способ 3. Оптимизация через временные таблицы

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

Выполним следующие шаги:

  1. Создадим временную таблицу ВТ_Контакты, куда выберем данные регистра только по нужным нам видам (адрес и телефон) и только для актуальных объектов.
  2. Проиндексируем временную таблицу по полю Объект.
  3. Соединим основной запрос с этой временной таблицей.

Это позволит оптимизатору SQL-запросов работать с гораздо меньшим объемом данных, что значительно ускорит формирование отчета.

Способ 4. Использование вложенных запросов в списке выбора

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


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

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

Что делать, если данных одного вида несколько?

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

В такой ситуации у нас есть два пути:

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

Рекомендации при работе с БСП

Если вы работаете в современной конфигурации (УТ 11, ERP, БП 3.0), где используется Библиотека стандартных подсистем (БСП), крайне рекомендуем использовать программные методы получения контактной информации. Разработчики 1С уже предусмотрели проблему дублей и оптимизации. Обратите внимание на метод:

УправлениеКонтактнойИнформацией.СформироватьЗапросДляВыводаКонтактнойИнформации()

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

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

← На главную