Как правильно выбрать вложенную таблицу в запросе с итогами в 1С?

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

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

Почему вложенная таблица пустая при использовании итогов?

Рассмотрим ситуацию: вы написали запрос, в котором выбираете данные документа и его табличную часть Товары, а затем добавили группировку по документу через ИТОГИ. Проблема заключается в иерархической структуре результата запроса. Когда вы выполняете метод Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам...), система возвращает вам строки-итоги. В этих строках агрегируются числовые данные (суммы, количества), но вложенная таблица по своей сути является набором строк, а не агрегатом.

Проанализируем причину: На уровне итоговой записи по документу поле вложенной таблицы всегда будет содержать NULL или пустую выборку. Платформа 1С не может «схлопнуть» несколько строк табличной части в одну ячейку итоговой строки. Данные вложенной таблицы физически присутствуют только на самом нижнем уровне — на уровне детальных записей.

Решение 1: Правильный обход иерархии (Детальные записи)

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


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

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

// 1. Получаем выборку по группировке (Документ)
ВыборкаДокумент = Результат.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам);

Пока ВыборкаДокумент.Следующий() Цикл
    
    // В этой точке ВыборкаДокумент.ТЧТовары будет пустой!
    // Нужно получить детальные записи:
    ВыборкаДетальные = ВыборкаДокумент.Выбрать();
    
    Пока ВыборкаДетальные.Следующий() Цикл
        // А вот теперь получаем вложенную таблицу из детальной записи
        ВыборкаТовары = ВыборкаДетальные.ТЧТовары.Выбрать();
        
        Пока ВыборкаТовары.Следующий() Цикл
            Сообщить(ВыборкаТовары.Номенклатура);
        КонецЦикла;
    КонецЦикла;
    
КонецЦикла;

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

Решение 2: Использование пакетных запросов и метода ВыбратьСледующийПоЗначениюПоля

Если запрос становится сложным, использование вложенных таблиц (конструкция Документ.Товары.(...)) считается плохим тоном (антипаттерном) из-за проблем с производительностью. Платформа для каждой строки основного запроса может выполнять отдельный подзапрос к базе данных, что при большом количестве документов приведет к фатальному замедлению.

Рассмотрим более профессиональный подход — Пакетные запросы. Мы разделим получение шапок и табличных частей на два отдельных запроса в одном пакете.

  1. В первом запросе выбираем данные документов и помещаем их во временную таблицу.
  2. Во втором запросе выбираем данные табличных частей, соединяя их с временной таблицей.
  3. Используем метод ВыбратьСледующийПоЗначениюПоля для эффективной склейки данных в коде 1С.

Пример реализации этой логики:


Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ Ссылка КАК Документ ПОМЕСТИТЬ ВТ_Доки ИЗ Документ.ПоступлениеТоваровУслуг;
    |ВЫБРАТЬ Документ, Контрагент, СуммаДокумента ИЗ Документ.ПоступлениеТоваровУслуг ГДЕ Ссылка В (ВЫБРАТЬ Документ ИЗ ВТ_Доки);
    |ВЫБРАТЬ Ссылка КАК Документ, Номенклатура, Количество ИЗ Документ.ПоступлениеТоваровУслуг.Товары ГДЕ Ссылка В (ВЫБРАТЬ Документ ИЗ ВТ_Доки) УПОРЯДОЧИТЬ ПО Документ";

Результаты = Запрос.ВыполнитьПакет();
ВыборкаШапка = Результаты[1].Выбрать();
ВыборкаТЧ = Результаты[2].Выбрать();

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

Решение 3: Левое соединение с табличной частью (Рекомендуемое)

Самый прозрачный и производительный способ — отказаться от вложенных таблиц в списке ВЫБРАТЬ и использовать ЛЕВОЕ СОЕДИНЕНИЕ с табличной частью. При этом итоги будут строиться по ссылке на документ.

Посмотрим на пример:


Запрос.Текст = "ВЫБРАТЬ
               |	Док.Ссылка КАК Документ,
               |	Док.Контрагент КАК Контрагент,
               |	Док.СуммаДокумента КАК СуммаДокумента,
               |	ТЧ.Номенклатура КАК Номенклатура,
               |	ТЧ.Количество КАК Количество
               |ИЗ
               |	Документ.ПоступлениеТоваровУслуг КАК Док
               |		ЛЕВОЕ СОЕДИНЕНИЕ Документ.ПоступлениеТоваровУслуг.Товары КАК ТЧ
               |		ПО Док.Ссылка = ТЧ.Ссылка
               |ИТОГИ
               |	МАКСИМУМ(Контрагент),
               |	МАКСИМУМ(СуммаДокумента)
               |ПО
               |	Документ";

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

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

Важные рекомендации по производительности

Проанализируем ситуацию с точки зрения нагрузки на сервер БД. Использование вложенных таблиц (Solution 1) заставляет СУБД формировать тяжелые XML-подобные структуры или выполнять серию подзапросов. Если ваша задача — вывести отчет по тысячам документов, выбирайте Решение 3 (Соединения) или Решение 2 (Пакетные запросы).

Основные выводы:

  1. Никогда не пытайтесь получить данные из поля вложенной таблицы непосредственно в строке ИТОГИ. Там их нет.
  2. Для простых задач и небольших выборок используйте ВыборкаДетальные = ВыборкаГруппировка.Выбрать().
  3. Для высоконагруженных систем используйте ЛЕВОЕ СОЕДИНЕНИЕ с табличной частью. Помните, что числовые реквизиты шапки (например, СуммаДокумента) в этом случае нельзя суммировать в итогах (СУММА), иначе они умножатся на количество строк в ТЧ. Используйте МАКСИМУМ.
  4. Метод ВыбратьСледующийПоЗначениюПоля — отличный способ избежать «каши» из данных при обработке больших объемов информации в коде.

Таким образом, мы выяснили, что отсутствие данных во вложенной таблице при использовании итогов — это не ошибка, а особенность архитектуры запросов 1С. Выбор правильного метода обхода результата зависит от сложности вашего запроса и требований к скорости работы системы.

← На главную