Как правильно заполнить дерево значений из пакета запросов в 1С 8.3

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

Работа с иерархическими структурами данных, такими как ДеревоЗначений, является одной из классических задач при разработке на платформе 1С:Предприятие 8.3. Особую сложность вызывает ситуация, когда данные для дерева необходимо получить не из одного простого запроса, а из пакета запросов, результаты которых могут быть логически связаны между собой. Разберем детально, как эффективно решить эту задачу, избегая типичных ошибок производительности и логики.

Анализ исходной ситуации

Проанализируем задачу: имеется пакетный запрос, состоящий из нескольких частей. Автор сообщения столкнулся с проблемой, когда реквизиты для заполнения строк дерева находятся в разных результатах пакета. При этом в коде часто допускаются ошибки обращения к индексам массива результатов. Рассмотрим пример некорректного кода, где дважды вызывается один и тот же индекс ВыборкаЗатрата[0] для разных логических сущностей, что приводит к потере данных второго запроса.

Для правильной работы нам необходимо не просто перебирать выборки, а использовать механизмы платформы, предназначенные для работы с иерархией. Выясним, какие подходы существуют для решения этой проблемы.

Способ 1. Использование конструкции ИТОГИ в теле запроса

Это наиболее «чистый» и производительный метод. Если данные в запросах связаны, нам не следует пытаться объединять их в коде на стороне сервера 1С. Вместо этого мы перенесем логику объединения на сторону СУБД. Посмотрим на алгоритм действий:

  1. Поместим результаты первого запроса во временную таблицу ВТ_Данные1.
  2. Поместим результаты второго запроса во временную таблицу ВТ_Данные2.
  3. В финальном запросе пакета выполним ЛЕВОЕ СОЕДИНЕНИЕ или ПОЛНОЕ СОЕДИНЕНИЕ этих таблиц по ключевому полю (например, Номенклатура или СтатьяЗатрат).
  4. Используем предложение ИТОГИ ПО... для формирования иерархии прямо в результате запроса.

При таком подходе заполнение дерева сводится к одной строке кода. Рассмотрим пример реализации на сервере:


// Предположим, Запрос уже содержит пакет с финальным запросом и ИТОГАМИ
РезультатыПакета = Запрос.ВыполнитьПакет();

// Получаем последний результат из массива (индекс последнего элемента)
ФинальныйРезультат = РезультатыПакета[РезультатыПакета.Количество() - 1];

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

Использование ОбходРезультатаЗапроса.ПоГруппировкам автоматически создает подчиненные строки в объекте ДеревоЗначений, соответствующие уровням итогов в запросе. Это значительно быстрее, чем ручной обход выборки в цикле Пока ... Следующий().

Способ 2. Ручное объединение через индексированные таблицы

Иногда запросы в пакете принципиально нельзя связать внутри текста SQL (например, они обращаются к разным базам через ВнешниеИсточникиДанных или имеют несовместимые типы связей). В этом случае проанализируем ситуацию с точки зрения оптимизации алгоритмов. Нам потребуется объединить данные вручную. Рассмотрим по шагам:

  1. Выгрузим результаты пакета в отдельные таблицы значений.
  2. Для таблицы, которая будет служить источником дополнительных данных, создадим индекс по ключевому полю связи.
  3. Обойдем основную таблицу и заполним дерево, подтягивая данные из второй таблицы методом Найти().

Посмотрим на пример кода, реализующего этот метод:


Результаты = Запрос.ВыполнитьПакет();
ТЗ_Основная = Результаты[0].Выгрузить();
ТЗ_ДопДанные = Результаты[1].Выгрузить();

// Индексация критически важна для производительности
ТЗ_ДопДанные.Индексы.Добавить("КлючСвязи");

Дерево = Новый ДеревоЗначений;
// Добавляем колонки из обеих таблиц
// ... (код добавления колонок)

Для Каждого СтрокаОсновная Из ТЗ_Основная Цикл
    НоваяСтрока = Дерево.Строки.Добавить();
    ЗаполнитьЗначенияСвойств(НоваяСтрока, СтрокаОсновная);
    
    // Ищем данные во второй таблице по индексу
    СтрокаДоп = ТЗ_ДопДанные.Найти(СтрокаОсновная.КлючСвязи, "КлючСвязи");
    Если СтрокаДоп <> Неопределено Тогда
        ЗаполнитьЗначенияСвойств(НоваяСтрока, СтрокаДоп);
    КонецЕсли;
КонецЦикла;

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

Способ 3. Использование промежуточных данных пакета

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

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

Особенности отображения на управляемых формах (УФ)

Важно помнить техническую особенность: объект ДеревоЗначений является серверным и не может быть передан на клиент. На управляемой форме данные хранятся в специальном типе ДанныеФормыДерево. Разберем процесс преобразования данных из запроса в реквизит формы:

  1. На сервере получаем ДеревоЗначений любым из описанных выше способов.
  2. Убеждаемся, что структура колонок реквизита формы совпадает со структурой колонок дерева.
  3. Используем метод ЗначениеВРеквизитФормы(ДеревоНаСервере, "ИмяРеквизитаДереваНаФорме").

Пример интеграции с формой:


&НаСервере
Процедура ЗаполнитьДеревоНаФорме()
    Запрос = Новый Запрос("ВЫБРАТЬ ... ИТОГИ ПО ...");
    Результат = Запрос.Выполнить();
    
    // Создаем объект ДеревоЗначений
    ДеревоОбъект = Результат.Выгрузить(ОбходРезультатаЗапроса.ПоГруппировкам);
    
    // Копируем данные в реквизит управляемой формы
    ЗначениеВРеквизитФормы(ДеревоОбъект, "ДеревоДанныхНаФорме");
КонецПроцедуры

Анализ типичных ошибок

Выясним, почему код из сообщения №4 на форуме не работал корректно. Основная причина — обращение к ВыборкаЗатрата[0] дважды. В пакете запросов каждый запрос имеет свой порядковый номер (индекс). Если в пакете 2 запроса, то первый результат доступен по индексу [0], а второй — по индексу [1].

Также использование конструкции Если Выборка.Следующий() Тогда Пока Выборка.Следующий() является ошибочным, так как первая запись «проглатывается» условием Если и не попадает в цикл Пока. Правильнее использовать просто цикл Пока, либо метод Выгрузить(), который мы рассмотрели ранее.

Таким образом, для эффективного заполнения дерева значений из пакета запросов рекомендуется отдавать предпочтение соединению данных внутри самого запроса с использованием ИТОГИ. Это обеспечивает максимальное быстродействие и минимизирует объем кода на языке 1С.

← На главную