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