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