Как получить в запросе связь с первичным документом для последнего в цепочке взаимосвязанных?

Программист 1С v8.3 (Обычные формы) Управленческий учет Торговля и дистрибуция
← На главную

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

Вариант 1. Использование рекурсивной функции в общем модуле

Это один из самых гибких методов, который позволяет обойти цепочку любой длины без изменения структуры конфигурации. Суть заключается в использовании «Вычисляемых полей» СКД для вызова функции, которая программно найдет корень цепочки. Разберем этот процесс по шагам.

Во-первых, нам необходимо создать или использовать существующий общий модуль. У модуля должны быть установлены флаги Сервер, Внешнее соединение и Вызов сервера. Пропишем в нем функцию, которая будет циклически искать первичный документ:


Функция ПолучитьПервичныйДокумент(ТекущийДокумент) Экспорт
	
	Если ТекущийДокумент = Неопределено Или ТекущийДокумент.Пустая() Тогда
		Возврат Неопределено;
	КонецЕсли;
	
	Основание = ТекущийДокумент.ДокументОснование;
	
	// Проверяем тип основания. Если это уже первичный отчет - возвращаем его.
	// Если это корректировка - идем глубже по цепочке.
	Если ТипЗнч(Основание) = Тип("ДокументСсылка.ОтчетКомиссионера") Тогда
		Возврат Основание;
	ИначеЕсли ТипЗнч(Основание) = Тип("ДокументСсылка.КорректировкаОтчетаКомиссионера") Тогда
		Возврат ПолучитьПервичныйДокумент(Основание); // Рекурсивный вызов
	Иначе
		Возврат Основание; // Если основание другого типа, возвращаем как есть
	КонецЕсли;
	
КонецФункции

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

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

Вариант 2. Иерархические связи наборов данных в СКД

Если мы хотим остаться в рамках механизмов СКД без написания кода в модулях, можно использовать механизм Связи наборов данных. Рассмотрим, как имитировать рекурсию встроенными средствами компоновки.

  1. Создадим два набора данных — запроса.
  2. НаборДанных1 выбирает основные документы корректировок за период.
  3. НаборДанных2 выбирает пары «Документ — Его основание».
  4. На вкладке Связи наборов данных настроим связь от НаборДанных1 к НаборДанных2.
  5. В качестве источника связи укажем поле ДокументОснование, а в качестве приемника — поле Ссылка.
  6. Установим флаг Иерархическая связь.

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

Вариант 3. Ограниченное количество соединений в запросе

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

Рассмотрим пример запроса, который «пробивает» до трех уровней вложенности:


ВЫБРАТЬ
	Корр.Ссылка КАК ДокументКорректировки,
	ВЫБОР
		КОГДА Корр.ДокументОснование ССЫЛКА Документ.ОтчетКомиссионера
			ТОГДА Корр.ДокументОснование
		КОГДА Корр.ДокументОснование.ДокументОснование ССЫЛКА Документ.ОтчетКомиссионера
			ТОГДА Корр.ДокументОснование.ДокументОснование
		КОГДА Корр.ДокументОснование.ДокументОснование.ДокументОснование ССЫЛКА Документ.ОтчетКомиссионера
			ТОГДА Корр.ДокументОснование.ДокументОснование.ДокументОснование
		ИНАЧЕ Корр.ДокументОснование
	КОНЕЦ КАК ПервичныйДокумент
ИЗ
	Документ.КорректировкаОтчетаКомиссионера КАК Корр

Преимущество этого метода в его молниеносной скорости на уровне SQL-запроса. Система выполнит несколько Join-ов по индексам. Недостаток очевиден — если в цепочке появится 11-й документ, отчет покажет неверные данные.

Вариант 4. Программная подготовка данных (Набор данных - Объект)

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

Мы можем написать алгоритм, который сначала выберет все корректировки, затем в цикле Пока будет соединять их с таблицей оснований, пока не закончатся новые уровни вложенности. Полученную таблицу «Документ — ПервичныйДокумент» мы передаем в СКД как Набор данных - объект.


// Пример логики в коде:
ТЗ_Связей = Новый ТаблицаЗначений;
ТЗ_Связей.Колонки.Добавить("Документ");
ТЗ_Связей.Колонки.Добавить("Первичный");

// 1. Заполняем ТЗ всеми корректировками
// 2. В цикле обходим ТЗ, для каждой строки вызываем поиск корня
// 3. Передаем готовую ТЗ в компоновщик
ВнешниеНаборыДанных = Новый Структура;
ВнешниеНаборыДанных.Вставить("ТаблицаСвязей", ТЗ_Связей);

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

Вариант 5. Оптимизация архитектуры: Регистр сведений

Рассмотрим долгосрочное решение проблемы. Если подобные отчеты требуются часто, постоянный поиск «корня» по всей цепочке — это лишняя нагрузка. В современных конфигурациях (например, ERP 2.5) для таких целей заводятся технические регистры сведений.

Мы можем создать в УПП простой регистр сведений СвязиДокументовДляОтчетов с измерениями Документ и КорневойДокумент. При проведении любой корректировки мы будем записывать в этот регистр связь:

  1. Если основание — первичный отчет, записываем: Документ = ЭтаКорректировка, КорневойДокумент = Отчет.
  2. Если основание — другая корректировка, находим в регистре запись для этого основания, берем оттуда КорневойДокумент и записываем для себя.

В этом случае запрос в СКД превращается в простейшее левое соединение с одной таблицей регистра, что работает мгновенно вне зависимости от глубины цепочки.

Выводы и рекомендации

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

← На главную