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