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