При разработке отчетов или создании печатных форм в системе «1С:Предприятие 8.3» программисты часто сталкиваются с задачей получения контактной информации контрагентов — номеров телефонов, адресов электронной почты или юридических адресов. Начинающие разработчики нередко совершают ошибку, пытаясь найти эти данные непосредственно в реквизитах справочника Контрагенты. Однако в типовых конфигурациях эти сведения хранятся обособленно.
В этой статье мы подробно разберем, как устроено хранение контактной информации в различных версиях конфигураций 1С, как написать эффективный и производительный запрос для извлечения телефона и почты, избегая дублирования строк, и как объединить несколько телефонных номеров одного контрагента в одну строку. Полученные контактные данные могут служить критериями для сопоставления объектов через перенос данных между похожими конфигурациями 1С при выполнении точечной миграции.
Прежде чем писать текст запроса, нам необходимо определить, какая архитектурная модель хранения контактной информации используется в вашей конфигурации. На платформе «1С:Предприятие 8.3» сосуществуют два основных подхода:
Объект хранит ссылку на владельца (контрагента, партнера, физическое лицо), а измерения или реквизиты определяют тип и вид информации.Справочник.Контрагенты.КонтактнаяИнформация.Понимание этой разницы критически важно, так как запрос, написанный для одной конфигурации, выдаст ошибку отсутствия таблицы в другой. Проблему различия форматов при миграции решает перенос данных УПП 1.3 — ERP, который автоматически преобразует данные под новую архитектуру целевой базы.
Рассмотрим ситуацию, когда контактные данные хранятся в регистре сведений РегистрСведений.КонтактнаяИнформация. Типичной ошибкой новичка является построение запроса с одним левым соединением и последующей фильтрацией через условие ГДЕ с оператором ИЛИ. Посмотрим на этот пример, чтобы понять, почему он работает неэффективно:
// Пример неоптимального запроса, приводящего к дублированию строк
ВЫБРАТЬ
Контрагенты.Ссылка КАК Покупатель,
ВЫБОР КОГДА КонтактнаяИнформация.Тип = ЗНАЧЕНИЕ(Перечисление.ТипыКонтактнойИнформации.Телефон)
ТОГДА КонтактнаяИнформация.Представление КОНЕЦ КАК Телефон,
ВЫБОР КОГДА КонтактнаяИнформация.Тип = ЗНАЧЕНИЕ(Перечисление.ТипыКонтактнойИнформации.АдресЭлектроннойПочты)
ТОГДА КонтактнаяИнформация.Представление КОНЕЦ КАК Почта
ИЗ
Справочник.Контрагенты КАК Контрагенты
ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.КонтактнаяИнформация КАК КонтактнаяИнформация
ПО КонтактнаяИнформация.Объект = Контрагенты.Ссылка
ГДЕ
Контрагенты.Покупатель = ИСТИНА
И (КонтактнаяИнформация.Тип = ЗНАЧЕНИЕ(Перечисление.ТипыКонтактнойИнформации.Телефон)
ИЛИ КонтактнаяИнформация.Тип = ЗНАЧЕНИЕ(Перечисление.ТипыКонтактнойИнформации.АдресЭлектроннойПочты))
Почему такой подход нежелателен?
NULL), во второй строке — заполнена почта (а телефон будет NULL).ИЛИ в секции ГДЕ или в условиях соединения ухудшает производительность запроса и мешает СУБД эффективно использовать индексы таблиц.Для того чтобы получить телефон и почту в одной строке без дублирования записей, мы применим раздельные левые соединения к одной и той же таблице регистра сведений. Каждое соединение будет отвечать исключительно за свой тип контактной информации. Условия фильтрации по типу данных мы вынесем непосредственно в параметры связи ПО.
Проанализируем оптимальный текст запроса:
ВЫБРАТЬ
Контрагенты.Ссылка КАК Покупатель,
Контрагенты.ЮрФизЛицо КАК ЮрФизЛицо,
ЕСТЬNULL(КонтактнаяИнформацияТелефон.Представление, "") КАК Телефон,
ЕСТЬNULL(КонтактнаяИнформацияПочта.Представление, "") КАК Почта
ИЗ
Справочник.Контрагенты КАК Контрагенты
// Первое соединение — только для получения телефона
ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.КонтактнаяИнформация КАК КонтактнаяИнформацияТелефон
ПО (КонтактнаяИнформацияТелефон.Объект = Контрагенты.Ссылка)
И (КонтактнаяИнформацияТелефон.Тип = ЗНАЧЕНИЕ(Перечисление.ТипыКонтактнойИнформации.Телефон))
// Второе соединение — только для получения адреса электронной почты
ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.КонтактнаяИнформация КАК КонтактнаяИнформацияПочта
ПО (КонтактнаяИнформацияПочта.Объект = Контрагенты.Ссылка)
И (КонтактнаяИнформацияПочта.Тип = ЗНАЧЕНИЕ(Перечисление.ТипыКонтактнойИнформации.АдресЭлектроннойПочты))
ГДЕ
Контрагенты.Покупатель = ИСТИНА
Преимущества данного решения:
ИЛИ, что позволяет оптимизатору СУБД построить быстрый план выполнения запроса.ЕСТЬNULL гарантирует, что при отсутствии контактных данных в ячейках отчета отобразится пустая строка, а не системное значение NULL.Когда мы соединяем справочник контрагентов с регистром сведений, необходимо заботиться о производительности базы данных. Если в вашей базе десятки тысяч контрагентов, левое соединение «всех со всеми» без установленных фильтров приведет к сильному падению скорости работы системы.
Если вам требуется получить данные не по всем покупателям, а по конкретному списку или одному контрагенту, обязательно передавайте этот отбор в секцию ГДЕ через параметры запроса:
ГДЕ
Контрагенты.Ссылка В (&СписокКонтрагентов)
Это позволит СУБД мгновенно отфильтровать выборку по индексу и присоединить контактную информацию только к выбранным записям.
В современных решениях на базе БСП контактная информация находится в табличной части справочника Контрагенты. Структура запроса принципиально похожа на предыдущий вариант, но вместо регистра сведений мы соединяем справочник с его собственной табличной частью КонтактнаяИнформация:
ВЫБРАТЬ
Контрагенты.Ссылка КАК Покупатель,
ЕСТЬNULL(КИ_Телефон.Представление, "") КАК Телефон,
ЕСТЬNULL(КИ_Почта.Представление, "") КАК Почта
ИЗ
Справочник.Contragents КАК Контрагенты // (В зависимости от локализации метаданных)
ЛЕВОЕ СОЕДИНЕНИЕ Справочник.Контрагенты.КонтактнаяИнформация КАК КИ_Телефон
ПО (КИ_Telephone.Ссылка = Контрагенты.Ссылка)
И (КИ_Telephone.Тип = ЗНАЧЕНИЕ(Перечисление.ТипыКонтактнойИнформации.Телефон))
ЛЕВОЕ СОЕДИНЕНИЕ Справочник.Контрагенты.КонтактнаяИнформация КАК КИ_Почта
ПО (КИ_Почта.Ссылка = Контрагенты.Ссылка)
И (КИ_Почта.Тип = ЗНАЧЕНИЕ(Перечисление.ТипыКонтактнойИнформации.АдресЭлектроннойПочты))
Обратите внимание на важный нюанс современных версий БСП:
Внутри табличной части КонтактнаяИнформация детальные сведения (например, код города, индекс, город, улица) хранятся в сериализованном виде — в полях ЗначенияПолей (формат XML по схемам XDTO) и Значение (формат JSON). Распарсить эти строки внутри запроса средствами платформы невозможно. Поэтому через запрос мы можем надежно получить только готовое строковое представление КИ из поля Представление.
В реальной разработке в конфигурациях, использующих БСП, писать прямые запросы к табличным частям контактной информации для получения представлений адресов или телефонов считается плохой практикой (bad practice). Структура метаданных может измениться при очередном обновлении БСП, что приведет к неработоспособности ваших доработок.
Для безопасного и быстрого извлечения контактной информации рекомендуется использовать готовые функции программного интерфейса общего модуля УправлениеКонтактнойИнформацией. Для быстрого поиска подобных стандартных функций и ознакомления с примерами их вызова в коде можно использовать справочник по методам БСП с примерами использования.
// Возвращает строку представления или таблицу со всеми полями адреса/телефона
КонтрагентСсылка = Справочники.Контрагенты.НайтиПоРеквизиту("ИНН", "7700000000");
ВидАдреса = Справочники.ВидыКонтактнойИнформации.ЮрАдресКонтрагента;
ПредставлениеАдреса = УправлениеКонтактнойИнформацией.КонтактнаяИнформацияОбъекта(
КонтрагентСсылка,
ВидАдреса,
ТекущаяДата(),
Истина // Возвращать только представление строкой
);
Если вам нужно получить данные по массиву контрагентов (например, при выводе списка в табличный документ), используйте пакетный метод. Под капотом БСП сама выполнит быстрый и правильный запрос к базе данных:
МассивКонтрагентов = Новый Массив;
// Заполняем массив ссылками...
МассивКонтрагентов.Добавить(Ссылка1);
МассивКонтрагентов.Добавить(Ссылка2);
// Определяем типы КИ, которые хотим получить
МассивТипов = Новый Массив;
МассивТипов.Добавить(Перечисления.ТипыКонтактнойИнформации.Телефон);
МассивТипов.Добавить(Перечисления.ТипыКонтактнойИнформации.АдресЭлектроннойПочты);
// Получаем таблицу значений с результатами
ТаблицаРезультат = УправлениеКонтактнойИнформацией.КонтактнаяИнформацияОбъектов(
МассивКонтрагентов,
МассивТипов
);
Рассмотрим более сложную практическую задачу. Что делать, если у одного контрагента в базе данных зарегистрировано несколько телефонных номеров (например, рабочий и мобильный)?
При использовании стандартного левого соединения в результате запроса мы неизбежно получим дублирование строк контрагента по количеству его телефонов. Как «схлопнуть» эти строки и вывести все телефоны контрагента в одной ячейке через запятую?
Важное ограничение платформы 1С:
В языке запросов 1С **отсутствуют** встроенные агрегатные функции для конкатенации строк (такие как GROUP_CONCAT в MySQL или STRING_AGG в MS SQL Server). Объединить произвольное количество строк в одну ячейку напрямую в СУБД средствами запроса 1С невозможно.
Тем не менее, эту задачу можно элегантно решить тремя альтернативными способами.
Если ваш отчет или выборка выводятся через СКД, платформа предоставляет встроенный механизм склеивания строк на уровне вычисления ресурсов:
Телефон) в список ресурсов.СоединитьСтроки(Массив(Телефон)) или СоединитьСтроки(ВычислитьВыражениеСГруппировкойМассив("Представление", "Покупатель")).
Если данные выводятся программно (например, при заполнении макета или формировании дерева значений), мы можем обработать результат первоначального запроса в цикле:
// 1. Выполняем базовый запрос, который возвращает дублирующие строки контрагентов
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Контрагенты.Ссылка КАК Контрагент,
| КИ.Представление КАК Телефон
|ИЗ
| Справочник.Контрагенты КАК Контрагенты
| ВНУТРЕННЕЕ СОЕДИНЕНИЕ РегистрСведений.КонтактнаяИнформация КАК КИ
| ПО КИ.Объект = Контрагенты.Ссылка
| И КИ.Тип = ЗНАЧЕНИЕ(Перечисление.ТипыКонтактнойИнформации.Телефон)
|УПОРЯДОЧИТЬ ПО
| Контрагент";
РезультатЗапроса = Запрос.Выполнить();
// 2. Создаем результирующую Таблицу Значений для свертки данных
ТаблицаТелефонов = Новый ТаблицаЗначений;
ТаблицаТелефонов.Колонки.Добавить("Контрагент");
ТаблицаТелефонов.Колонки.Добавить("Телефоны");
СоответствиеКонтрагентов = Новый Соответствие;
Выборка = РезультатЗапроса.Выбрать();
Пока Выборка.Следующий() Цикл
ТекущийСписокТелефонов = СоответствиеКонтрагентов[Выборка.Контрагент];
Если ТекущийСписокТелефонов = Неопределено Тогда
СоответствиеКонтрагентов.Вставить(Выборка.Контрагент, Выборка.Телефон);
Иначе
// Склеиваем телефоны через запятую
НовыйСписокТелефонов = ТекущийСписокТелефонов + ", " + Выборка.Телефон;
СоответствиеКонтрагентов.Вставить(Выборка.Контрагент, НовыйСписокТелефонов);
КонецЕсли;
КонецЦикла;
// 3. Переносим сгруппированные данные в ТаблицуЗначений
Для Каждого КлючИЗначение Из СоответствиеКонтрагентов Цикл
НоваяСтрока = ТаблицаТелефонов.Добавить();
НоваяСтрока.Контрагент = КлючИЗначение.Ключ;
НоваяСтрока.Телефоны = КлючИЗначение.Значение;
КонецЦикла;
Если бизнес-требования жестко ограничивают количество выводимых телефонов (например, требуется вывести строго не более двух телефонов: основной и дополнительный), задачу можно решить исключительно в запросе с помощью временных таблиц и нумерации строк:
// Временная таблица со всеми телефонами контрагентов
ВЫБРАТЬ
КИ.Объект КАК Контрагент,
КИ.Представление КАК Телефон
ПОМЕСТИТЬ ВТ_Телефоны
ИЗ
РегистрСведений.КонтактнаяИнформация КАК КИ
ГДЕ
КИ.Тип = ЗНАЧЕНИЕ(Перечисление.ТипыКонтактнойИнформации.Телефон)
;
////////////////////////////////////////////////////////////////////////////////
// Нумеруем телефоны и соединяем их для вывода первых двух номеров
ВЫБРАТЬ
Контрагенты.Ссылка КАК Покупатель,
// Выбираем первый попавшийся телефон
МИНИМУМ(ВТ1.Телефон) КАК Телефон1,
// Выбираем второй телефон (который больше первого)
МИНИМУМ(ВТ2.Телефон) КАК Телефон2
ПОМЕСТИТЬ ВТ_СгруппированныеТелефоны
ИЗ
Справочник.Контрагенты КАК Контрагенты
ЛЕВОЕ СОЕДИНЕНИЕ ВТ_Телефоны КАК ВТ1
ПО ВТ1.Контрагент = Контрагенты.Ссылка
ЛЕВОЕ СОЕДИНЕНИЕ ВТ_Телефоны КАК ВТ2
ПО ВТ2.Контрагент = Контрагенты.Ссылка
И ВТ2.Телефон > ВТ1.Телефон
ГРУППИРОВАТЬ ПО
Контрагенты.Ссылка
;
////////////////////////////////////////////////////////////////////////////////
// Итоговый запрос со склеиванием двух полей в одну строку
ВЫБРАТЬ
ВТ.Покупатель,
ВТ.Телефон1 + ВЫБОР
КОГДА ВТ.Телефон2 ЕСТЬ NULL ТОГДА ""
ИНАЧЕ ", " + ВТ.Телефон2
КОНЕЦ КАК ТелефоныСтрокой
ИЗ
ВТ_СгруппированныеТелефоны КАК ВТ
Этот способ достаточно сложен и подходит только тогда, когда количество записей на одного контрагента ограничено и детерминировано. Для объединения произвольного количества строк всегда выбирайте Способ 1 (СКД) или Способ 2 (программный обход).
Мы детально изучили методы работы с контактной информацией в 1С 8.3. Подводя итог, выделим несколько важных правил для качественной разработки:
ПО. Это избавит вас от дублирования строк и оптимизирует нагрузку на СУБД.ГДЕ, чтобы запрос не обрабатывал всю базу данных впустую.УправлениеКонтактнойИнформацией.СоединитьСтроки() либо обрабатывайте результат запроса кодом в цикле.