Разузлование номенклатуры 1с это

Обновлено: 02.07.2024

Задача, рассматриваемая в статье, приводится здесь с разрешения ее автора Ish_2. Собственно задача была сформулирована в ходе обсуждения вопроса "Реально написать хитрый запрос" в посте (79) примерно так:

Дана таблица, содержащая три колонки: номенклатура-набор (X), номенклатура-элемент (Y), количество элементов в наборе (Z). В строчках этой таблицы записаны тройки (X, Y, Z):

Всего таблица содержит 61 элемент ("0", А0, . А9, Б0, . , Б9, . , Е0, . , Е9) и 510 строк:

(0, А0, 2) (0, А1, 2) (0, А2, 2) (0, А3, 2) (0, А4, 2) (0, А5, 2) (0, А6, 2) (0, А7, 2) (0, А8, 2) (0, А9, 2)

(А0, Б0, 2) (А0, Б1, 2) (А0, Б2, 2) (А0, Б3, 2) (А0, Б4, 2) (А0, Б5, 2) (А0, Б6, 2) (А0, Б7, 2) (А0, Б8, 2) (А0, Б9, 2)

(А1, Б0, 2) (А1, Б1, 2) (А1, Б2, 2) (А1, Б3, 2) (А1, Б4, 2) (А1, Б5, 2) (А1, Б6, 2) (А1, Б7, 2) (А1, Б8, 2) (А1, Б9, 2)

(Д9, Е0, 2) (Д9, Е1, 2) (Д9, Е2, 2) (Д9, Е3, 2) (Д9, Е4, 2) (Д9, Е5, 2) (Д9, Е6, 2) (Д9, Е7, 2) (Д9, Е8, 2) (Д9, Е9, 2).

Каждая строка таблицы ( X , Y , Z ) интерпретируется следующим образом: объект X содержит элемент Y в количестве Z . Наглядно эта таблица представляется следующим графом (фиг1). Все дуги на фиг1 направлены слева направо и имеют вес, равный двум.


Требуется построить программу, способную посчитать: сколько элементов Е0, Е1, Е2, Е3, Е4, Е5, Е6, Е7, Е8, Е9 содержится в объекте «0». Конечно, программа должна решать подобные задачи в общем случае для всех подобных таблиц. Тогда данный пример будет тестом скорости работы программы, а также правильности алгоритма путем сравнения с очевидным ответом (6 400 000, 6 400 000, … , 6 400 000). В этом общем случае программа также должна исключать ошибочные связи типа Е9 – 0, Д0 – Б9 и другие «обратные» связи, приводящие к «зацикливанию».

Сравнение строк 11 (А0-Б0-2) и 21 (А1-Б0-2) позволяет сделать принципиально важный вывод: структура, описываемая таблицей, не является деревом! Элемент Б0 имеет более одного владельца! Необходимость такой структуры при составлении сборочных спецификаций легко объяснить: если четыре колеса входят в спецификацию автомобиля, а два таких же колеса в спецификацию прицепа, то заводится не две разных, а одна единая спецификация колеса. В результате шина как элемент колеса входит в состав автомобиля с прицепом двумя различными способами: в составе колеса в составе автомобиля и в составе колеса в составе прицепа.

В тестовом примере элемент Е9 входит в состав элемента «0» сто тысячью (100 000) различных способов! Вот тут-то некоторые программисты и делают принципиальную ошибку: они считают, что для подсчета числа вхождений Е9 нужно перечислить все 100 000 способов. (На самом деле это тоже самое, что вычислять 10 х 10 х 10 х 10 х 10 х 10 миллионом сложений единиц!) Источником этой ошибки, видимо, является известный отчет о разузловании, строчки которого соответствуют путям вхождения атомарных деталей в готовое изделие. Отчет для наглядности представляется в виде дерева. В данном случае это дерево отчета будет иметь миллион конечных вершин. Видимо, тут и возникает желание получить результат поставленной задачи группировкой этих путей по виду их конечных элементов. Как же по-другому? – А вот как:

1. Исходная задача разузлования заданного узла разбивается на более простые задачи разузлования отдельных узлов, причем в этих простых задачах считается, что результат разузлования подчиненных узлов уже известен. Тогда результат разузлования узла получается как сумма результатов разузлования подчиненных узлов, взятых с коэффициентом количества для каждого подчиненного узла. Если обозначить результат разузлования узла j как Q ( j ) = ( q 1 j , q 2 j , … , qNj ), где qij обозначает, что узел j транзитивно содержит узел i qij раз, и если как gkl обозначить количество узлов i в спецификации узла k , то получаем простую формулу подсчета результата разузлования узла k

Q(k) = ∑ i=1,N G’(i) * Q(i) = ∑ i=1,N gki * qij. (1)

2. Результат разузлования каждого узла запоминается, чтобы не вычисляться заново. Поэтому в решении тестовой задачи будет всего 51 операция вида (1).

3. Порядок обхода вершин графа, начиная с заданного, определяется рекурсией. Дважды в один и тот же узел для расчетов алгоритм не входит!

Вот конфигурация справочника, в котором хранится таблица (фиг2)


А вот код самой программы (фиг3)


Приведен весь исходный код. Никакого другого кода в решении нет и он не требуется.

Для работы с относительно небольшими структурами достаточно использовать массивы.

В данном случае используется массив массивов Матрица. Измерениями будет номер вершины графа. Для простоты считаем, что коды вершин начинаются с единицы и не содержат пропусков. Это позволяет рассчитать индекс при обращении к массиву как ё = Ссылка.Код – 1. В результате Матрица[ё] будет содержать разузлование узла Ссылка. Если Матрица[ё][0] = Неопределено, значит, этой вершиной еще не занимались. Сначала разузлование обнуляется //процедура Обнулить()//, затем к нему в цикле прибавляются разузлования подчиненных узлов //определяемые рекурсивно процедурой Распаковать()// с соответствующим весом //процедура Добавить()//. После разузлования подчиненных узлов отмечаем единицей текущий узел //Матрица[ё][ё] = 1//. Этой единицей данный узел будет с точки зрения вышележащих уровней.

Если разузлование находится в процессе расчета //Матрица[ё][0] <> Неопределено//, но не закончено, то Матрица[ё][ё] = 0. Это дает возможность проверить зацикливание. Правда диагностический вывод «слабоват» - мы отмечаем только вершину, в которую попали в результате «зацикливания».

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

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

Сейчас тестовая задача выполняется примерно 0,122 секунды (использовалась платформа 8.1 и ноутбук). При этом «неправильные» методы дают на два-три порядка большие значения!

Сформировав структуру степени 10 из 33 уровней (331 вершина), получим время разузлования всего 3,666 секунды!

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

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

Программисты при произнесении слов "граф" или "дерево" , не задумываясь продолжают : "Понятно.. это рекурсия !" . Не вдаваясь в дискуссию о том , почему рекурсия в БД есть синоним неэффективности, мы на простом примере рассмотрим решение задачи "разузлования" номенклатуры принципиально "нерекурсивным" методом. Выберем эпиграф для статьи :

"Запрос в БД - это сила. Рекурсия - это зло ."


§ 1. Где и зачем это нужно ?

На предприятиях ,осуществляющих сборку сложных изделий (летательных аппаратов, электровозов, судов), состоящих из тысяч и десятков тысяч комплектующих, актуальна задача ведения спецификаций изделий.В статье мы рассмотрим одну из частей этой задачи - "разузлование" номенклатуры : по спецификации изделия определить состав и количество "элементарных", входящих в него составляющих. Все известные способы решения такой задачи - "медленные"и время выполнения может доходить в реальных задачах до нескольких минут.
Какой же из "медленных" способов - самый быстрый, надежный , эффективный ?
Достаточно ли встроенных возможностей платформы 1с для решения таких задач ?


§ 2. Постановка задачи.

На нижеприведенном рисунке изображено два способа отображения графа "Спецификации" :


Рис. 1. Два способа отображения графа.

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


Рис. 2. Выходная таблица.

§ 3. Решение.

Преобразуем заданную в условии задачи таблицу "Спецификации", добавив колонку
"КодНоменклатуры" :


Рис. 4. Таблица "Спецификации".

Создадим временную таблицу "ВремТаблица0" с единственной записью ,содержащей
номенклатуру "Электровоз" :


Рис. 5. Начальная таблица ВремТаблица0.

Начальные данные заданы. Рассмотрим код обработки :

ТекстЗапроса = "ВЫБРАТЬ
| ЕСТЬNULL(Спец.Номенклатура, Исх.Номенклатура) КАК Номенклатура,
| Исх.Количество * ЕСТЬNULL(Спец.Количество, 1) КАК Количество,
| Исх.СтрокаКодов + ЕСТЬNULL(Спец.КодНоменклатуры, """") КАК СтрокаКодов,
| ВЫБОР
| КОГДА Исх.ПризнакКонцаВетки > 0
| ТОГДА Исх.ПризнакКонцаВетки
| КОГДА Спец.КодНоменклатуры ЕСТЬ NULL
| ТОГДА 1 // нормальное завершение ветки
| КОГДА Исх.СтрокаКодов ПОДОБНО Спец.КодНоменклатуры
| ТОГДА 2 // зацикливание
| ИНАЧЕ 0 // ветка продолжается
| КОНЕЦ КАК ПризнакКонцаВетки
|ПОМЕСТИТЬ Последующая
|ИЗ
| Исходная КАК Исх
| ЛЕВОЕ СОЕДИНЕНИЕ Спецификация КАК Спец
| ПО (Исх.ПризнакКонцаВетки = 0) // соединяемся только тогда, когда ветка продолжается
| И Исх.Номенклатура = Спец.Владелец
|;
|УНИЧТОЖИТЬ Исходная
|;
|ВЫБРАТЬ ПЕРВЫЕ 1 Посл.Номенклатура
|ИЗ Последующая КАК Посл
|ГДЕ Посл.ПризнакКонцаВетки = 0" ;

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

На рис. 5-8 расмотрены промежуточные временные таблицы алгоритма во всех циклах решения.


Рис. 5 Таблица ВремТаблица0.


Рис. 6. Вид таблицы после исполнения первого цикла ( перед началом второго).
В колонке ""СтрокаКодов" накапливаются все коды номенклатуры текущей ветки.


Рис. 7. Вид таблицы после исполнения второго цикла . Обратите внимание ,
обнаружено зацикливание , но работа "по графу" продолжается. Следующий цикл
- третий.


Рис. 8. Вид таблицы после исполнения третьего цикла. Все записи таблицы имеют
ненулевой ПризнакКонцаВетки - осуществляется выход из цикла.

ВремТаблица3 содержит все необходимые данные для вывода результатов решения :
- графа ошибок ( по колонке "СтрокаКодов" для номенклатуры "Электровоз" получаем дерево "Ошибки")
- выходной плоской таблицы номенклатур

- применен последовательный поуровневый обход графа , т.е . принципиально "нерекурсивный" метод решения.
- количество обращений к БД при таком обходе равно уровню графа , увеличенному на 1.
- 99.9% времени исполнения алгоритма занимает выполнения запросов к БД .


§ 4. Тест по быстродействию.

Чтобы оценка быстродействия была предельно конкретной , в качестве среды выберем - демонстрационную конфигурация БП 1.6(2.0) и представим два лёгких теста начального заполнения справочника "СпецификацииНоменклатуры", реализованных в обработке "ЗапросПротиврекурсии.epf". Нижеприведенные тесты-"миллионники", надеюсь, с лихвой перекрывают все возможные случаи медленного "разузлования" в реальных задачах. Действительно , практически невозможно представить себе предприятия , на которых бы справочник Номенклатура имел размер более 300 000 записей и уровень графа, описывающего Спецификацию сложного изделия, превышал бы число 20.
Тест № 1. "Десять"
Справочник "СпецификацииНоменклатуры" содержит 6-уровневый 10-арный граф , имеющий 1 000 000 различных ветвей и при обходе такого графа таблица ВремТаблица6 на выходе цикла алгоритма содержит 1 000 000 записей. Но сам справочник "СпецификацииНоменклатуры" в своей табличной части содержит лишь 60 записей.
Проще говоря , корневому элементу подчинены 10 элементов, каждому из которых подчинен одинаковый набор из десятка следующих элементов и т.д.

Тест № 2. "Два"
Справочник "СпецификацииНоменклатуры" содержит 20-уровневый бинарный граф , имеющий 1 048 576 различных ветвей и при обходе такого графа таблица ВремТаблица20 содержит 1 048 576 записей. Но сам справочник "СпецификацииНоменклатуры" в своей табличной части содержит лишь 40 записей.


Рис. 9. Быстрое (4-6 сек) создание тестового примера в обработке "ЗапросПротивРекурсии.epf"


Небольшой объём исходных данных в тестах №1 и №2 порождает иллюзию : сейчас загрузим всё в таблицу значений и в оперативной памяти всё "быстренько" порешаем с помощью рекурсии. Прозрение к любителю рекурсии приходит после замера времени выполнения. Автор провёл опрос среди знакомых специалистов и для Теста №1 получил следующую информацию (приводится обобщенно , со слов авторов) : Разузлование номенклатуры для Теста № 1 140-220 сек в файловом варианте. Понимая всю условность приведенной информации , автор самостоятельно реализовал простейшую имитацию рекурсионного алгоритма :

Рис. 10. Имитация рекурсионного алгортима. Количество циклов 1 111 111 равно количеству узлов в графе
теста №1.

В этой имитации "ничего нет". В ней заведомо многократно меньше операторов , таблица значений пуста, затратный контроль зацикливания не осуществляется и т.д. Время выполнения этой имитации лишь на 15%-20% меньше времени выполнения обработки "ЗапросПротивРекурсии.epf" в тесте №1 . Стал очевиден качественный вывод : рекурсионный алгоритм проигрывает и проигрывает много.

Приведем результаты тестирования для обработки "ЗапросПротивРекурсии.epf". Замеры производились для "теплого"(второго) запуска обработки как в клиент -серверном, так и в файловом варианте. Нижние пределы диапазонов приведены как оценки значений, полученных на компьютере автора. Про верхние пределы диапазонов : скажем так , автору не удалось найти такие серверы и такие рабочие станции для файлового режима , на которых бы время выполнения было хуже. Итак , оценки ( подчеркиваю , оценки ) для "среднеофисных" серверов и локальных станций с оперативной памятью 1Гб.


Тест № 1 "Десять"

Для файлового варианта


Тест № 2 "Два"

Для файлового варианта

Рис.11. Результаты тестирования.

§ 5. Для любопытных.

В статье ни слова не сказано о сравнении по быстродействию с другими не-1совскими реализациями решения тестов №1и №2. А вдруг всё дело только в том , что 1с-овский интерпретатор неэффективен ? Может быть , если реализовать задачу на Си или Delfi, то удастся достичь лучшего быстродействия, чем указано в теме ? Или , может быть , использовать "чисто" TSQL ? Прогноз автора : НЕТ. НЕ ПОМОЖЕТ. Такая Рекурсия всё равно проиграет запросу 1с. Но буду рад ,если появятся публикации на эту тему с другими мнениями.
Прошу только помнить, что в статье идет речь не о "заточке" для тестов №1 и № 2, а об универсальной обработке произвольного графа ( в общем случае, циклического), которая " в среднем" оптимальна, на взгляд автора, для решения задачи "разузлования" на предприятии. Это значит , что таблица Спецификации может иметь миллионы записей и это существенно не скажется на быстродействии представленной обработки (в клиент-серверном варианте). Это значит также , что алгоритм различные графы и деревья , содержащиеся в таблице Спецификации, обрабатывает одинаково и априорно (на входе) не имеет никакой "подсказывающей "информации о характере графа .

Второй вопрос , который никак не затронут в статье : как нам не попасть на "миллиард" ? как оптимизировать работу с повторениями элементов при "разузловании" ? Можно заметить ,в частности, что при отсутствии контроля зацикленности можно было вставить в демонстрационный запрос "СГРУППИРОВАТЬ ПО " и тогда время выполнения по тесту № 1 было бы меньше 2 сек. Подход же с контролем зацикленности сложнее, и, думаю, мало востребован в практических задачах учета. "Миллиард" оставим без рассмотрения. В обработке "ЗапросПротивРекурсии.epf" лишь применено ограничение на размер промежуточных временных таблиц.

§ 6. Заключение

Автор убежден , что встроенных возможностей платформы 1с без использования рекурсии вполне достаточно для создания надежного и эффективного механизма "разузлования".
Главное же преимущество, на взгляд автора, представленной демонстрационной обработки не в том , что она выиграет по скорости выполнения у какой-то другой "рекурсионной обработки", а в том , что она более технологична и надежна - вся тяжесть вычислений (99.9%) ложится на сервер БД и мы "отвязываемся" от слабости клиента.

Статья была уже написана , когда я на ИС получил информацию и поверхностно познакомился с подходами к задаче "разузлования" , применяемыми в реальных, "вполне взрослых" проектах . Подходы эти несколько смутили. Но не отменили главного вывода статьи : применение рекурсии в задачах "разузлования" ( в постановке см.§2) избыточно.


§ 7. Благодарности

Автор приносит благодарности всем участникам дискуссии , предшествующей появлению данной статьи :
Hogik , Tango , Wkbapka , Арчибальд , Ildarovich, ILM . Эти авторы разными способами и с разной силой толкали к пониманию новой для меня задачи "разузлования".Спасибо.

Вы две разные вещи не смешивайте. Иерархия и периодика.

Бывает и хуже. Когда А и Б можно заменить на М и Н. Но только вместе. Отдельно заменить нельзя

SQL прекрасен, но в прикладной области склоняюсь у чему-то типа LINQ
А где в ерп проихсодит разузлование по спецификациям? Я по слову разузлования поискал - нашел для себестоимости разузлование общего модуля. Разузлование как я понял реурсивное, с кэшем спецификаций. Но в гугле говорилось что разузлование в ЕРП на СКД. Подскажите, пожалуйста, действительно ли это так и где это найти?)
(0) ну Америку вы не открыли, ни разу. 1С никогда не была мегабыстродействующей системой. Ее козыри - удобство разработки за счет мощной библиотеки прикладных объектов и работоспособная трехуровневая модель работы с данными. При этом удобство и универсальность часто играют достаточно злую шутку, например, с оптимальностью запросов, которые приходится выполнять SQL-серверу.
(39) Были же темы. Рекурсивное разузлование на СКД Что там точно в ERP, не знаю, наверное вариация метода, и вряд ли там открыли Америку в плане производительности. Плюс куча тем на Инфостарте, но некоторые требуют стартмани.
" от готовой продукции до сырья разузловать продукцию по спецификации.
Конкретно - колбасу с учетом аналогов до сырья."
.
и получите с учетом аналогов N вариантов наборов исходного сырья, так?
.
а в чем проблема 1Синой разузловать? или разузловывается сотня готовых изделий по 100 раз в минуту?

(45) Очень долго разузловывается, технологи хотят пол базы разузловывать за 10 секунд, а сейчас это происходит минут за 20, их это не устраивает.

Подскажите, пожалуйста. По примеру на статье на СКД получил запрос

И это работает. И я понимаю как это работает. Выбираем спецификацию а потом в цикле ищем спецификации основания. А вот как сделать то что мне надо не пойму(
А надо мне . Спецификации.ИсходныеКомплектующие -> Спецификации.ВыходныеИзделия связь по ссылке -> Спецификации.ИсходныеКомплектующие свяpь по номенклатуре -> Спецификации.ВыходныеИзделия связь по ссылке -> Спецификации.ИсходныеКомплектующие свяpь по номенклатуре.

Не могу понять как написать. Может у кого-то есть идеи, пожалуйста?)


Программисты при произнесении слов "граф" или "дерево" , не задумываясь продолжают : "Понятно.. это рекурсия !" . Не вдаваясь в дискуссию о том , почему рекурсия в БД есть синоним неэффективности, мы на простом примере рассмотрим решение задачи "разузлования" номенклатуры принципиально "нерекурсивным" методом. Выберем эпиграф для статьи :

"Запрос в БД - это сила. Рекурсия - это зло ."


§ 1. Где и зачем это нужно ?

На предприятиях ,осуществляющих сборку сложных изделий (летательных аппаратов, электровозов, судов), состоящих из тысяч и десятков тысяч комплектующих, актуальна задача ведения спецификаций изделий.В статье мы рассмотрим одну из частей этой задачи - "разузлование" номенклатуры : по спецификации изделия определить состав и количество "элементарных", входящих в него составляющих. Все известные способы решения такой задачи - "медленные"и время выполнения может доходить в реальных задачах до нескольких минут.
Какой же из "медленных" способов - самый быстрый, надежный , эффективный ?
Достаточно ли встроенных возможностей платформы 1с для решения таких задач ?


§ 2. Постановка задачи.

На нижеприведенном рисунке изображено два способа отображения графа "Спецификации" :


Рис. 1. Два способа отображения графа.

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


Рис. 2. Выходная таблица.

§ 3. Решение.

Преобразуем заданную в условии задачи таблицу "Спецификации", добавив колонку
"КодНоменклатуры" :


Рис. 4. Таблица "Спецификации".

Создадим временную таблицу "ВремТаблица0" с единственной записью ,содержащей
номенклатуру "Электровоз" :


Рис. 5. Начальная таблица ВремТаблица0.

Начальные данные заданы. Рассмотрим код обработки :

ТекстЗапроса = "ВЫБРАТЬ
| ЕСТЬNULL(Спец.Номенклатура, Исх.Номенклатура) КАК Номенклатура,
| Исх.Количество * ЕСТЬNULL(Спец.Количество, 1) КАК Количество,
| Исх.СтрокаКодов + ЕСТЬNULL(Спец.КодНоменклатуры, """") КАК СтрокаКодов,
| ВЫБОР
| КОГДА Исх.ПризнакКонцаВетки > 0
| ТОГДА Исх.ПризнакКонцаВетки
| КОГДА Спец.КодНоменклатуры ЕСТЬ NULL
| ТОГДА 1 // нормальное завершение ветки
| КОГДА Исх.СтрокаКодов ПОДОБНО Спец.КодНоменклатуры
| ТОГДА 2 // зацикливание
| ИНАЧЕ 0 // ветка продолжается
| КОНЕЦ КАК ПризнакКонцаВетки
|ПОМЕСТИТЬ Последующая
|ИЗ
| Исходная КАК Исх
| ЛЕВОЕ СОЕДИНЕНИЕ Спецификация КАК Спец
| ПО (Исх.ПризнакКонцаВетки = 0) // соединяемся только тогда, когда ветка продолжается
| И Исх.Номенклатура = Спец.Владелец
|;
|УНИЧТОЖИТЬ Исходная
|;
|ВЫБРАТЬ ПЕРВЫЕ 1 Посл.Номенклатура
|ИЗ Последующая КАК Посл
|ГДЕ Посл.ПризнакКонцаВетки = 0" ;

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

На рис. 5-8 расмотрены промежуточные временные таблицы алгоритма во всех циклах решения.


Рис. 5 Таблица ВремТаблица0.


Рис. 6. Вид таблицы после исполнения первого цикла ( перед началом второго).
В колонке ""СтрокаКодов" накапливаются все коды номенклатуры текущей ветки.


Рис. 7. Вид таблицы после исполнения второго цикла . Обратите внимание ,
обнаружено зацикливание , но работа "по графу" продолжается. Следующий цикл
- третий.


Рис. 8. Вид таблицы после исполнения третьего цикла. Все записи таблицы имеют
ненулевой ПризнакКонцаВетки - осуществляется выход из цикла.

ВремТаблица3 содержит все необходимые данные для вывода результатов решения :
- графа ошибок ( по колонке "СтрокаКодов" для номенклатуры "Электровоз" получаем дерево "Ошибки")
- выходной плоской таблицы номенклатур

- применен последовательный поуровневый обход графа , т.е . принципиально "нерекурсивный" метод решения.
- количество обращений к БД при таком обходе равно уровню графа , увеличенному на 1.
- 99.9% времени исполнения алгоритма занимает выполнения запросов к БД .


§ 4. Тест по быстродействию.

Чтобы оценка быстродействия была предельно конкретной , в качестве среды выберем - демонстрационную конфигурация БП 1.6(2.0) и представим два лёгких теста начального заполнения справочника "СпецификацииНоменклатуры", реализованных в обработке "ЗапросПротиврекурсии.epf". Нижеприведенные тесты-"миллионники", надеюсь, с лихвой перекрывают все возможные случаи медленного "разузлования" в реальных задачах. Действительно , практически невозможно представить себе предприятия , на которых бы справочник Номенклатура имел размер более 300 000 записей и уровень графа, описывающего Спецификацию сложного изделия, превышал бы число 20.
Тест № 1. "Десять"
Справочник "СпецификацииНоменклатуры" содержит 6-уровневый 10-арный граф , имеющий 1 000 000 различных ветвей и при обходе такого графа таблица ВремТаблица6 на выходе цикла алгоритма содержит 1 000 000 записей. Но сам справочник "СпецификацииНоменклатуры" в своей табличной части содержит лишь 60 записей.
Проще говоря , корневому элементу подчинены 10 элементов, каждому из которых подчинен одинаковый набор из десятка следующих элементов и т.д.

Тест № 2. "Два"
Справочник "СпецификацииНоменклатуры" содержит 20-уровневый бинарный граф , имеющий 1 048 576 различных ветвей и при обходе такого графа таблица ВремТаблица20 содержит 1 048 576 записей. Но сам справочник "СпецификацииНоменклатуры" в своей табличной части содержит лишь 40 записей.


Рис. 9. Быстрое (4-6 сек) создание тестового примера в обработке "ЗапросПротивРекурсии.epf"


Небольшой объём исходных данных в тестах №1 и №2 порождает иллюзию : сейчас загрузим всё в таблицу значений и в оперативной памяти всё "быстренько" порешаем с помощью рекурсии. Прозрение к любителю рекурсии приходит после замера времени выполнения. Автор провёл опрос среди знакомых специалистов и для Теста №1 получил следующую информацию (приводится обобщенно , со слов авторов) : Разузлование номенклатуры для Теста № 1 140-220 сек в файловом варианте. Понимая всю условность приведенной информации , автор самостоятельно реализовал простейшую имитацию рекурсионного алгоритма :

Рис. 10. Имитация рекурсионного алгортима. Количество циклов 1 111 111 равно количеству узлов в графе
теста №1.

В этой имитации "ничего нет". В ней заведомо многократно меньше операторов , таблица значений пуста, затратный контроль зацикливания не осуществляется и т.д. Время выполнения этой имитации лишь на 15%-20% меньше времени выполнения обработки "ЗапросПротивРекурсии.epf" в тесте №1 . Стал очевиден качественный вывод : рекурсионный алгоритм проигрывает и проигрывает много.

Приведем результаты тестирования для обработки "ЗапросПротивРекурсии.epf". Замеры производились для "теплого"(второго) запуска обработки как в клиент -серверном, так и в файловом варианте. Нижние пределы диапазонов приведены как оценки значений, полученных на компьютере автора. Про верхние пределы диапазонов : скажем так , автору не удалось найти такие серверы и такие рабочие станции для файлового режима , на которых бы время выполнения было хуже. Итак , оценки ( подчеркиваю , оценки ) для "среднеофисных" серверов и локальных станций с оперативной памятью 1Гб.


Тест № 1 "Десять"

Для файлового варианта


Тест № 2 "Два"

Для файлового варианта

Рис.11. Результаты тестирования.

§ 5. Для любопытных.

В статье ни слова не сказано о сравнении по быстродействию с другими не-1совскими реализациями решения тестов №1и №2. А вдруг всё дело только в том , что 1с-овский интерпретатор неэффективен ? Может быть , если реализовать задачу на Си или Delfi, то удастся достичь лучшего быстродействия, чем указано в теме ? Или , может быть , использовать "чисто" TSQL ? Прогноз автора : НЕТ. НЕ ПОМОЖЕТ. Такая Рекурсия всё равно проиграет запросу 1с. Но буду рад ,если появятся публикации на эту тему с другими мнениями.
Прошу только помнить, что в статье идет речь не о "заточке" для тестов №1 и № 2, а об универсальной обработке произвольного графа ( в общем случае, циклического), которая " в среднем" оптимальна, на взгляд автора, для решения задачи "разузлования" на предприятии. Это значит , что таблица Спецификации может иметь миллионы записей и это существенно не скажется на быстродействии представленной обработки (в клиент-серверном варианте). Это значит также , что алгоритм различные графы и деревья , содержащиеся в таблице Спецификации, обрабатывает одинаково и априорно (на входе) не имеет никакой "подсказывающей "информации о характере графа .

Второй вопрос , который никак не затронут в статье : как нам не попасть на "миллиард" ? как оптимизировать работу с повторениями элементов при "разузловании" ? Можно заметить ,в частности, что при отсутствии контроля зацикленности можно было вставить в демонстрационный запрос "СГРУППИРОВАТЬ ПО " и тогда время выполнения по тесту № 1 было бы меньше 2 сек. Подход же с контролем зацикленности сложнее, и, думаю, мало востребован в практических задачах учета. "Миллиард" оставим без рассмотрения. В обработке "ЗапросПротивРекурсии.epf" лишь применено ограничение на размер промежуточных временных таблиц.

§ 6. Заключение

Автор убежден , что встроенных возможностей платформы 1с без использования рекурсии вполне достаточно для создания надежного и эффективного механизма "разузлования".
Главное же преимущество, на взгляд автора, представленной демонстрационной обработки не в том , что она выиграет по скорости выполнения у какой-то другой "рекурсионной обработки", а в том , что она более технологична и надежна - вся тяжесть вычислений (99.9%) ложится на сервер БД и мы "отвязываемся" от слабости клиента.

Статья была уже написана , когда я на ИС получил информацию и поверхностно познакомился с подходами к задаче "разузлования" , применяемыми в реальных, "вполне взрослых" проектах . Подходы эти несколько смутили. Но не отменили главного вывода статьи : применение рекурсии в задачах "разузлования" ( в постановке см.§2) избыточно.


§ 7. Благодарности

Автор приносит благодарности всем участникам дискуссии , предшествующей появлению данной статьи :
Hogik , Tango , Wkbapka , Арчибальд , Ildarovich, ILM . Эти авторы разными способами и с разной силой толкали к пониманию новой для меня задачи "разузлования".Спасибо.

Читайте также: