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