Как в одном запросе 1С выбрать данные из нескольких табличных частей документа?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

При выгрузке данных в форматы обмена (например, XML или JSON) разработчики часто сталкиваются с задачей: необходимо получить заголовочную часть документа и строки сразу нескольких независимых табличных частей (например, Товары и Услуги). При этом важно организовать удобный иерархический обход без лишней нагрузки на базу данных и сервер.

Попытка соединить несколько табличных частей в одном простом запросе через ЛЕВОЕ СОЕДИНЕНИЕ по полю Ссылка приводит к классической проблеме — декартовому произведению строк. Если в документе содержится 10 строк с товарами и 5 строк с услугами, плоское соединение сформирует 50 строк выборки, дублируя данные шапки и табличных частей. Разберем правильные и эффективные способы решения этой задачи в платформе 1С:Предприятие.

Способ 1. Использование встроенного механизма вложенных таблиц

Платформа 1С поддерживает специальный синтаксис запросов, позволяющий извлекать данные табличных частей непосредственно в составе выборки родительского документа с помощью конструкции ИмяТабличнойЧасти.(Поля). В этом случае в результат запроса табличная часть попадает как отдельное поле специального типа.

Посмотрим на пример текста запроса, где выбираются реквизиты шапки и конкретные примитивные поля связанных справочников из двух разных табличных частей:


ВЫБРАТЬ
    РеализацияТоваровУслуг.Номер КАК НомерДокумента,
    РеализацияТоваровУслуг.Дата КАК ДатаДокумента,
    РеализацияТоваровУслуг.Контрагент.Наименование КАК КонтрагентНаименование,
    РеализацияТоваровУслуг.Товары.(
        Номенклатура.Наименование КАК НаименованиеТовара,
        Номенклатура.Артикул КАК АртикулТовара,
        Количество КАК Количество,
        Сумма КАК Сумма
    ) КАК ТабличнаяЧастьТовары,
    РеализацияТоваровУслуг.Услуги.(
        Номенклатура.Наименование КАК НаименованиеУслуги,
        Количество КАК Количество,
        Сумма КАК Сумма
    ) КАК ТабличнаяЧастьУслуги
ИЗ
    Документ.РеализацияТоваровУслуг КАК РеализацияТоваровУслуг
ГДЕ
    РеализацияТоваровУслуг.Проведен = ИСТИНА

Разберем подробнее, как устроен обход полученных данных во встроенном языке. При таком синтаксисе поля ТабличнаяЧастьТовары и ТабличнаяЧастьУслуги внутри текущей строки документа имеют тип РезультатЗапроса. Чтобы обойти строки табличной части, мы вызываем метод Выбрать() непосредственно у соответствующего поля:


Результат = Запрос.Выполнить();
ВыборкаДокумент = Результат.Выбрать();

Пока ВыборкаДокумент.Следующий() Цикл
    // Чтение данных шапки документа
    Номер = ВыборкаДокумент.НомерДокумента;
    ДатаДок = ВыборкаДокумент.ДатаДокумента;
    Контрагент = ВыборкаДокумент.КонтрагентНаименование;
    
    // Вложенный обход строк табличной части "Товары"
    ВыборкаТовары = ВыборкаДокумент.ТабличнаяЧастьТовары.Выбрать();
    Пока ВыборкаТовары.Следующий() Цикл
        ТоварНаименование = ВыборкаТовары.НаименованиеТовара;
        ТоварКоличество = ВыборкаТовары.Количество;
    КонецЦикла;
    
    // Вложенный обход строк табличной части "Услуги"
    ВыборкаУслуги = ВыборкаДокумент.ТабличнаяЧастьУслуги.Выбрать();
    Пока ВыборкаУслуги.Следующий() Цикл
        УслугаНаименование = ВыборкаУслуги.НаименованиеУслуги;
        УслугаСумма = ВыборкаУслуги.Сумма;
    КонецЦикла;
КонецЦикла;

Рассмотрим ограничения, присущие механизму вложенных таблиц, которые важно учитывать при проектировании архитектуры:

  1. Несовместимость с временными таблицами: запрос, содержащий вложенные таблицы, нельзя выгрузить во временную таблицу конструкцией ПОМЕСТИТЬ.
  2. Отсутствие группировок верхнего уровня: в запросе нельзя использовать секцию СГРУППИРОВАТЬ ПО для объединения данных шапки.
  3. Порядок строк: нельзя гибко отсортировать строки внутри вложенной таблицы через предложение УПОРЯДОЧИТЬ ПО; порядок строк сохраняется в соответствии с системным полем НомерСтроки.
  4. Потребление оперативной памяти: для каждой строки верхнего уровня платформа инициализирует объект РезультатЗапроса, что при выборке десятков тысяч документов может создавать высокую нагрузку на память сервера 1С.

Способ 2. Объединение табличных частей через «ОБЪЕДИНИТЬ ВСЕ» и секцию «ИТОГИ ПО»

Если требуется обойти ограничения вложенных таблиц и получить единый плоский набор данных с многоуровневым иерархическим обходом, применим объединение строк с искусственным разделением типов записей.

Проанализируем структуру такого запроса:


ВЫБРАТЬ
    Реализация.Ссылка КАК ДокументСсылка,
    Реализация.Номер КАК НомерДокумента,
    "Товары" КАК ИмяТЧ,
    РеализацияТовары.Номенклатура.Наименование КАК Наименование,
    РеализацияТовары.Количество КАК Количество
ИЗ
    Документ.РеализацияТоваровУслуг.Товары КАК РеализацияТовары
    ВНУТРЕННЕЕ СОЕДИНЕНИЕ Документ.РеализацияТоваровУслуг КАК Реализация
        ПО РеализацияТовары.Ссылка = Реализация.Ссылка

ОБЪЕДИНИТЬ ВСЕ

ВЫБРАТЬ
    Реализация.Ссылка,
    Реализация.Номер,
    "Услуги",
    РеализацияУслуги.Номенклатура.Наименование,
    РеализацияУслуги.Количество
ИЗ
    Документ.РеализацияТоваровУслуг.Услуги КАК РеализацияУслуги
    ВНУТРЕННЕЕ СОЕДИНЕНИЕ Документ.РеализацияТоваровУслуг КАК Реализация
        ПО РеализацияУслуги.Ссылка = Реализация.Ссылка

ИТОГИ ПО
    ДокументСсылка,
    ИмяТЧ

Для чтения результатов такого запроса используется иерархический обход по уровням группировок:


Результат = Запрос.Выполнить();

// 1 уровень: Документ
ВыборкаДокумент = Результат.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам);
Пока ВыборкаДокумент.Следующий() Цикл
    НомерДок = ВыборкаДокумент.НомерДокумента;
    
    // 2 уровень: Табличная часть (Товары или Услуги)
    ВыборкаТЧ = ВыборкаДокумент.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам, "ИмяТЧ");
    Пока ВыборкаТЧ.Следующий() Цикл
        ТипТЧ = ВыборкаТЧ.ИмяТЧ;
        
        // 3 уровень: Детальные строки конкретной ТЧ
        ВыборкаСтрок = ВыборкаТЧ.Выбрать();
        Пока ВыборкаСтрок.Следующий() Цикл
            ИмяПозиции = ВыборкаСтрок.Наименование;
            Кол = ВыборкаСтрок.Количество;
        КонецЦикла;
    КонецЦикла;
КонецЦикла;

Способ 3. Пакетный запрос с временными таблицами (наиболее производительный вариант)

Когда требуется обработать большой объем документов, оптимальным с точки зрения скорости работы СУБД и расхода оперативной памяти является выполнение пакетного запроса через менеджер временных таблиц:

  1. Первый запрос отбирает ссылки нужных документов и сохраняет их во временную таблицу ВТ_Документы.
  2. Второй запрос выбирает реквизиты шапки из ВТ_Документы.
  3. Третий запрос выбирает строки Товары, соединяя их с ВТ_Документы.
  4. Четвертый запрос аналогично выбирает строки Услуги.

Вызов метода ВыполнитьПакет() возвращает массив результатов, каждый из которых можно быстро сопоставить (например, загрузив строки в таблицы значений с индексацией по ссылке или сформировав структуру соответствий). Этот метод полностью исключает декартово произведение и не порождает вложенных объектов выборок.

Рекомендация по формированию XML

Для максимального быстродействия не выгружайте результаты запроса в промежуточный объект ТаблицаЗначений. Используйте потоковую запись через объект ЗаписьXML:


ЗаписьXML = Новый ЗаписьXML;
ЗаписьXML.ОткрытьФайл(ПутьКФайлу, "UTF-8");
ЗаписьXML.ЗаписатьОбъявлениеXML();
ЗаписьXML.ЗаписатьНачалоЭлемента("ДанныеОбмена");

Пока ВыборкаДокумент.Следующий() Цикл
    ЗаписьXML.ЗаписатьНачалоЭлемента("Документ");
    ЗаписьXML.ЗаписатьАтрибут("Номер", Строка(ВыборкаДокумент.НомерДокумента));
    
    ЗаписьXML.ЗаписатьНачалоЭлемента("Товары");
    ВыборкаТовары = ВыборкаДокумент.ТабличнаяЧастьТовары.Выбрать();
    Пока ВыборкаТовары.Следующий() Цикл
        ЗаписьXML.ЗаписатьНачалоЭлемента("Товар");
        ЗаписьXML.ЗаписатьАтрибут("Наименование", Строка(ВыборкаТовары.НаименованиеТовара));
        ЗаписьXML.ЗаписатьКонецЭлемента(); // Товар
    КонецЦикла;
    ЗаписьXML.ЗаписатьКонецЭлемента(); // Товары
    
    ЗаписьXML.ЗаписатьКонецЭлемента(); // Документ
КонецЦикла;

ЗаписьXML.ЗаписатьКонецЭлемента(); // ДанныеОбмена
ЗаписьXML.Закрыть();

Такой подход обеспечивает минимальное потребление оперативной памяти даже при выгрузке сотен тысяч документов, так как данные не накапливаются в памяти, а сразу последовательно записываются на диск.

← На главную