При разработке в среде 1С:Предприятие программисты часто сталкиваются с необходимостью выборки данных из табличных частей справочников или документов. Иногда в старом коде или учебных материалах можно встретить специфический синтаксис выбора табличной части прямо в теле основного запроса с использованием круглых скобок. Однако при попытке обработать результаты такого запроса «в лоб» разработчики неизбежно сталкиваются с ошибками.
В данной статье мы подробно разберем, почему возникает эта проблема, как устроена работа со вложенными таблицами в 1С, как правильно написать современный и быстрый запрос к табличной части и почему за использование устаревших конструкций в профессиональном сообществе принято «бить канделябром».
Проанализируем ситуацию, с которой столкнулся автор темы. В его исходном запросе была написана следующая конструкция:
Запрос = Новый запрос;
Запрос.Текст = "ВЫБРАТЬ
| Абоненты.ТипТелефона КАК ТипТелефона,
| Абоненты.Данные.(ВидЛьготы КАК ВидЛьготы) КАК Данные
|ИЗ
| Справочник.Абоненты КАК Абоненты
|ГДЕ
| Абоненты.Ссылка = &Абонент";
При обходе результата запроса автор пытался напрямую присвоить значение вложенного поля реквизиту объекта:
Выгрузка = Результат.Выгрузить();
Для каждого Строка из Выгрузка Цикл
//Объект.ВидЛьготы = Строка.Данные; // Здесь происходила ошибка!
Объект.ТипТелефона = Строка.ТипТелефона;
КонецЦикла;
Выясним причину этой ошибки. Когда мы выбираем табличную часть (в данном случае Данные справочника Абоненты) внутри круглых скобок в основном списке полей, платформа 1С возвращает не простое значение и не ссылку, а целую вложенную таблицу. Тип данных в колонке Данные будет напрямую зависеть от того, каким методом мы обрабатываем результат запроса:
Результат.Выгрузить(), то в колонке Данные для каждой строки будет находиться полноценный объект типа ТаблицаЗначений.Результат.Выбрать(), то в поле Данные будет находиться объект типа РезультатЗапроса, к которому нужно применять дополнительный метод Выбрать().Попытка напрямую присвоить Объект.ВидЛьготы = Строка.Данные приводит к ошибке несоответствия типов, поскольку мы пытаемся записать целую таблицу в одно простое поле (реквизит) объекта.
Рассмотрим, как должен выглядеть код обработки, если бы мы по каким-то причинам были вынуждены оставить исходную структуру запроса без изменений.
Вариант А. Через выгрузку в Таблицу значений (метод Выгрузить())
Поскольку колонка Данные содержит вложенную таблицу значений, нам необходимо организовать вложенный цикл для ее обхода:
Выгрузка = Результат.Выгрузить();
Для каждого Строка из Выгрузка Цикл
Объект.ТипТелефона = Строка.ТипТелефона;
// Получаем вложенную ТаблицуЗначений из ячейки текущей строки
ТаблицаДанных = Строка.Данные;
Для каждого СтрокаДанные из ТаблицаДанных Цикл
// Обращаемся к реквизиту табличной части внутри вложенного цикла
Объект.ВидЛьготы = СтрокаДанные.ВидЛьготы;
КонецЦикла;
КонецЦикла;
Вариант Б. Через последовательный обход выборки (метод Выбрать())
Этот способ является более производительным и предпочтительным с точки зрения расхода оперативной памяти. Из поля вложенного запроса мы получаем подчиненную выборку:
Выборка = Результат.Выбрать();
Пока Выборка.Следующий() Цикл
Объект.ТипТелефона = Выборка.ТипТелефона;
// Получаем подчиненную выборку из поля РезультатаЗапроса
ВыборкаДанные = Выборка.Данные.Выбрать();
Пока ВыборкаДанные.Следующий() Цикл
Объект.ВидЛьготы = ВыборкаДанные.ВидЛьготы;
КонецЦикла;
КонецЦикла;
Как справедливо заметили участники форума, за конструкцию вида Абоненты.Данные.(ВидЛьготы КАК ВидЛьготы) в профессиональной разработке 1С давно принято критиковать. Данный синтаксис считается устаревшим (legacy-кодом) по ряду веских причин:
Проанализируем правильный подход, предложенный в Сообщении 4. Вместо того чтобы выбирать данные из основной таблицы справочника и пытаться «вытащить» из нее табличную часть вложенным полем, мы должны написать запрос напрямую к табличной части как к самостоятельной физической таблице.
Для этого в качестве источника данных в секции ИЗ мы указываем Справочник.Абоненты.Данные. Все реквизиты «шапки» (самого справочника-владельца) мы легко можем получить через точку от поля Ссылка.
Рассмотрим правильный текст запроса и его последующую обработку в коде:
Запрос = Новый запрос;
Запрос.Текст = "ВЫБРАТЬ
| АбонентыДанные.Ссылка.ТипТелефона КАК ТипТелефона,
| АбонентыДанные.ВидЛьготы КАК ВидЛьготы
|ИЗ
| Справочник.Абоненты.Данные КАК АбонентыДанные
|ГДЕ
| АбонентыДанные.Ссылка = &Абонент";
Запрос.УстановитьПараметр("Абонент", Объект.Абонент);
Результат = Запрос.Выполнить();
// Теперь мы получаем плоскую таблицу, которую очень легко обойти в один цикл
Выборка = Результат.Выбрать();
Пока Выборка.Следующий() Цикл
Объект.ТипТелефона = Выборка.ТипТелефона;
Объект.ВидЛьготы = Выборка.ВидЛьготы;
КонецЦикла;
Посмотрим на преимущества этого решения:
Ссылка.Иногда перед разработчиком стоят более сложные архитектурные задачи, при которых простого запроса к табличной части может не хватить. Разберем еще два профессиональных приема:
Если вам необходимо гарантированно получить данные «шапки» объекта (например, справочника или документа) даже в том случае, если его табличная часть абсолютно пуста, запроса напрямую к ТЧ будет недостаточно (так как пустая ТЧ не вернет ни одной строки). В этом случае мы используем левое соединение основной таблицы с ее табличной частью:
ВЫБРАТЬ
Абоненты.ТипТелефона КАК ТипТелефона,
АбонентыДанные.ВидЛьготы КАК ВидЛьготы
ИЗ
Справочник.Абоненты КАК Абоненты
ЛЕВОЕ СОЕДИНЕНИЕ Справочник.Абоненты.Данные КАК АбонентыДанные
ПО АбонентыДанные.Ссылка = Абоненты.Ссылка
ГДЕ
Абоненты.Ссылка = &Абонент
В сложных пакетных запросах со множеством соединений хорошим тоном считается предварительное помещение данных табличной части во временную таблицу с обязательным созданием индекса по ключевым полям:
// В первом запросе пакета выбираем ТЧ во Временную Таблицу
ВЫБРАТЬ
АбонентыДанные.Ссылка КАК Ссылка,
АбонентыДанные.ВидЛьготы КАК ВидЛьготы
ПОМЕСТИТЬ ВТ_ДанныеАбонентов
ИЗ
Справочник.Абоненты.Данные КАК АбонентыДанные
ГДЕ
АбонентыДанные.Ссылка = &Абонент
ИНДЕКСИРОВАТЬ ПО
Ссылка;
// Во втором запросе пакета соединяем ВТ с другими физическими таблицами
ВЫБРАТЬ
Абоненты.ТипТелефона КАК ТипТелефона,
ВТ.ВидЛьготы КАК ВидЛьготы
ИЗ
Справочник.Абоненты КАК Абоненты
ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТ_ДанныеАбонентов КАК ВТ
ПО ВТ.Ссылка = Абоненты.Ссылка
Использование временных таблиц гарантирует предсказуемость работы оптимизатора СУБД и исключает проблемы с производительностью при работе с большими базами данных.