В предыдущей статье был рассмотрен прием «матричного умножения» в расчете транзитивного замыкания отношений, его теоретическое обоснование и реализация на платформе «1С:Предприятие 8» на примере замыкания иерархии справочника. Из-за того, что данный прием хорошо ложится на возможности конструирования текста запроса на языке 1С, получаемый с использованием этого приема код оказывается очень компактным (всего 9 строк!) и быстрым. Возможно, у кого-то могло сложиться впечатление, что решением одной экзотической задачи с непонятным названием область применения рассмотренного метода и ограничивается. Однако, ЭТО НЕ ТАК! Существуют более приземленные практические задачи, где с большой выгодой можно применить разработанный прием построения запроса. В данной статье рассмотрены сразу пять таких задач.
1. Быстрое определение уровней всех элементов справочника одним пакетным запросом.
При использовании объектной модели для получения уровня элемента иерархического справочника можно использовать функцию «Уровень». Она показывает уровень вложенности текущего элемента справочника, при этом элементы в корне иерархии, вообще не имеющие родителей, имеют уровень «0».
У этой функции два недостатка. Во-первых, она медленно выполняется. Почему это так, понять несложно, если вспомнить, как хранится иерархия в таблицах СУБД. Во-вторых, функция «Уровень» не работает в запросе. А этого как раз очень часто и не хватает: наличие колонки, содержащей уровень иерархии элемента, упростило бы решение многих задач запросами.
Выходом может быть использование следующего запроса и построенной на нем функции
Функция УровниИерархии(ИмяСправочника, МаксимальнаяДлинаПути) Экспорт
Пролог = «ВЫБРАТЬ Родитель НачалоДуги, Ссылка КонецДуги ПОМЕСТИТЬ ЗамыканияДлины1 ИЗ Справочник.Номенклатура
| ГДЕ Родитель <> Значение(Справочник.Номенклатура.ПустаяСсылка)
| ОБЪЕДИНИТЬ ВЫБРАТЬ Ссылка, Ссылка ИЗ Справочник.Номенклатура;»;
Рефрен = «ВЫБРАТЬ РАЗЛИЧНЫЕ ПерваяДуга.НачалоДуги, ВтораяДуга.КонецДуги ПОМЕСТИТЬ ЗамыканияДлины#2 ИЗ ЗамыканияДлины#1 КАК ПерваяДуга
| СОЕДИНЕНИЕ ЗамыканияДлины#1 КАК ВтораяДуга ПО ПерваяДуга.КонецДуги = ВтораяДуга.НачалоДуги;
| УНИЧТОЖИТЬ ЗамыканияДлины#1;»;
Эпилог = «ВЫБРАТЬ КОЛИЧЕСТВО(НачалоДуги) — 1 Предок, КонецДуги Потомок ИЗ ЗамыканияДлины#2 СГРУППИРОВАТЬ ПО КонецДуги»;
Запрос = Новый Запрос(СтрЗаменить(Пролог, «Номенклатура», ИмяСправочника));
МаксимальнаяДлинаЗамыканий = 1;
Пока МаксимальнаяДлинаЗамыканий < МаксимальнаяДлинаПути Цикл
Запрос.Текст = Запрос.Текст + СтрЗаменить(СтрЗаменить(Рефрен, «#1», Формат(МаксимальнаяДлинаЗамыканий, «ЧГ=0»)), «#2», Формат(2 * МаксимальнаяДлинаЗамыканий, «ЧГ=0»));
МаксимальнаяДлинаЗамыканий = 2 * МаксимальнаяДлинаЗамыканий
КонецЦикла;
Запрос.Текст = Запрос.Текст + СтрЗаменить(Эпилог, «#2», Формат(МаксимальнаяДлинаЗамыканий, «ЧГ=0»));
Возврат Запрос.Выполнить().Выгрузить()
КонецФункции
Запрос в данной функции отличается от базового запроса из основной статьи только эпилогом. Работа функции построена на следующем наблюдении: так как таблица транзитивного замыкания содержит всех предков любого элемента, то, чтобы определить его уровень, нужно просто посчитать этих предков.
Разумеется, сконструированный внутри функции текст запроса не обязательно сразу выполнять. Его можно сделать частью более общего пакетного запроса, в котором будет использоваться получаемая на последнем этапе таблица. Это касается и всех следующих примеров.
Рассмотрим, для примера, следующую иерархию номенклатуры:
Замыкание иерархии вернет следующую таблицу:
Приведенная функция на основе подсчета предков в замыкании вернет следующую таблицу:
2. Быстрое определение максимальной глубины иерархии одним пакетным запросом.
Данная задача может возникнуть, например, при выводе иерархических списков, когда требуется заранее определить «высоту» (число этажей) отображения списка. Трудность задачи в том, что приходится просматривать все элементы справочника, для каждого из которых необходим вызов функции «Уровень». Хотя такой код весьма прост,
Функция МаксимальныйУровеньСправочника(ИмяСправочника, Ответ = 0) Экспорт
Выборка = Справочники[ИмяСправочника].Выбрать();
Пока Выборка.Следующий() Цикл Ответ = Макс(Ответ, Выборка.ПолучитьОбъект().Уровень())
КонецЦикла;
Возврат Ответ
КонецФункции
время его выполнения сильно и неприятно удивляет. В прилагаемой к статье обработке есть кнопка «Ой, глубина», которая вызывает написанную таким образом функцию и позволяет убедиться в большом времени ее работы. Конечно, можно использовать рекурсию, загрузив весь справочник в оперативную память, однако на больших справочниках применение рекурсии также будет не столь эффективным из-за большого количества отдельных вычислений. Пример использования рекурсии приведен здесь [http://nashe1c.ru/materials-view.jsp?id=371]. Решение не образцовое, однако доказывает интерес к данной теме.
В результате, наилучшим решением оказывается использование предлагаемого запроса в следующем виде:
Функция ГлубинаИерархии(ИмяСправочника, МаксимальнаяДлинаПути) Экспорт
Пролог = «ВЫБРАТЬ Родитель НачалоДуги, Ссылка КонецДуги ПОМЕСТИТЬ ЗамыканияДлины1 ИЗ Справочник.Номенклатура
| ГДЕ Родитель <> Значение(Справочник.Номенклатура.ПустаяСсылка)
| ОБЪЕДИНИТЬ ВЫБРАТЬ Ссылка, Ссылка ИЗ Справочник.Номенклатура;»;
Рефрен = «ВЫБРАТЬ РАЗЛИЧНЫЕ ПерваяДуга.НачалоДуги, ВтораяДуга.КонецДуги ПОМЕСТИТЬ ЗамыканияДлины#2 ИЗ ЗамыканияДлины#1 КАК ПерваяДуга
| СОЕДИНЕНИЕ ЗамыканияДлины#1 КАК ВтораяДуга ПО ПерваяДуга.КонецДуги = ВтораяДуга.НачалоДуги;
| УНИЧТОЖИТЬ ЗамыканияДлины#1;»;
Эпилог = «ВЫБРАТЬ ПЕРВЫЕ 1 КОЛИЧЕСТВО(НачалоДуги) — 1 Глубина, КонецДуги Потомок ИЗ ЗамыканияДлины#2 СГРУППИРОВАТЬ ПО КонецДуги УПОРЯДОЧИТЬ ПО КОЛИЧЕСТВО(НачалоДуги) — 1 УБЫВ»;
Запрос = Новый Запрос(СтрЗаменить(Пролог, «Номенклатура», ИмяСправочника));
МаксимальнаяДлинаЗамыканий = 1;
Пока МаксимальнаяДлинаЗамыканий < МаксимальнаяДлинаПути Цикл
Запрос.Текст = Запрос.Текст + СтрЗаменить(СтрЗаменить(Рефрен, «#1», Формат(МаксимальнаяДлинаЗамыканий, «ЧГ=0»)), «#2», Формат(2 * МаксимальнаяДлинаЗамыканий, «ЧГ=0»));
МаксимальнаяДлинаЗамыканий = 2 * МаксимальнаяДлинаЗамыканий
КонецЦикла;
Запрос.Текст = Запрос.Текст + СтрЗаменить(Эпилог, «#2», Формат(МаксимальнаяДлинаЗамыканий, «ЧГ=0»));
Возврат Запрос.Выполнить().Выгрузить()[0].Глубина
КонецФункции
Для краткости, случай, когда в справочнике нет ни одного элемента, не рассматривается.
Для того же примера глубина будет равна 3
3. Определение прародителя (родителя верхнего уровня) в пакетном запросе.
Судя по обсуждениям на форумах, этот вопрос встречается достаточно часто. Широко известно решение, использующее итоги по иерархии [в комментарии (6) к предыдущей статье]. Однако оно не подходит для того, чтобы использоваться в середине пакетного запроса, не дает простой возможности одновременного определения родителей верхнего уровня нескольких элементов и, вероятно, не работает максимально быстро. От этих недостатков свободно следующее решение
Функция Прародители(ИмяСправочника, МаксимальнаяДлинаПути) Экспорт
Пролог = «ВЫБРАТЬ Родитель НачалоДуги, Ссылка КонецДуги ПОМЕСТИТЬ ЗамыканияДлины1 ИЗ Справочник.Номенклатура
| ГДЕ Родитель <> Значение(Справочник.Номенклатура.ПустаяСсылка)
| ОБЪЕДИНИТЬ ВЫБРАТЬ Ссылка, Ссылка ИЗ Справочник.Номенклатура;»;
Рефрен = «ВЫБРАТЬ РАЗЛИЧНЫЕ ПерваяДуга.НачалоДуги, ВтораяДуга.КонецДуги ПОМЕСТИТЬ ЗамыканияДлины#2 ИЗ ЗамыканияДлины#1 КАК ПерваяДуга
| СОЕДИНЕНИЕ ЗамыканияДлины#1 КАК ВтораяДуга ПО ПерваяДуга.КонецДуги = ВтораяДуга.НачалоДуги;
| УНИЧТОЖИТЬ ЗамыканияДлины#1;»;
Эпилог = «ВЫБРАТЬ НачалоДуги Предок, КонецДуги Потомок ИЗ ЗамыканияДлины#2
| ГДЕ НачалоДуги <> КонецДуги И НачалоДуги.Родитель = Значение(Справочник.Номенклатура.ПустаяСсылка)»;
Запрос = Новый Запрос(СтрЗаменить(Пролог, «Номенклатура», ИмяСправочника));
МаксимальнаяДлинаЗамыканий = 1;
Пока МаксимальнаяДлинаЗамыканий < МаксимальнаяДлинаПути Цикл
Запрос.Текст = Запрос.Текст + СтрЗаменить(СтрЗаменить(Рефрен, «#1», Формат(МаксимальнаяДлинаЗамыканий, «ЧГ=0»)), «#2», Формат(2 * МаксимальнаяДлинаЗамыканий, «ЧГ=0»));
МаксимальнаяДлинаЗамыканий = 2 * МаксимальнаяДлинаЗамыканий
КонецЦикла;
Запрос.Текст = Запрос.Текст + СтрЗаменить(Эпилог, «#2», Формат(МаксимальнаяДлинаЗамыканий, «ЧГ=0»));
Возврат Запрос.Выполнить().Выгрузить()
КонецФункции
Для того же примера…
4. Быстрое определение циклов произвольной длины одним пакетным запросом.
Определение циклов основано на следующей идее:
Будем считать уровнем элемента количество прямо или косвенно «предшествующих» ему других элементов (в смысле конкретного отношения). Такой уровень на основе таблицы транзитивного замыкания легко посчитать для ориентированного графа любого вида. Нетрудно догадаться, что уровень всех элементов, находящихся в цикле, будет одинаков. Тогда признаком того, что дуга принадлежит циклу, будет одинаковый уровень ее концов.
В результате получаем следующий запрос, находящий дуги, принадлежащие циклу.
Функция ЦиклыСпецификацийУПП(МаксимальнаяДлинаПути) Экспорт
Пролог = «ВЫБРАТЬ Выход.Номенклатура НачалоДуги, Вход.Номенклатура КонецДуги, Выход.Ссылка ПОМЕСТИТЬ ИсходноеОтношение
| ИЗ Справочник.СпецификацииНоменклатуры.ВыходныеИзделия КАК Выход
| СОЕДИНЕНИЕ Справочник.СпецификацииНоменклатуры.ИсходныеКомплектующие КАК Вход ПО Выход.Ссылка = Вход.Ссылка
| ГДЕ Выход.Ссылка.Активная;
| ВЫБРАТЬ РАЗЛИЧНЫЕ НачалоДуги, КонецДуги ПОМЕСТИТЬ ЗамыканияДлины1 ИЗ ИсходноеОтношение
| ОБЪЕДИНИТЬ ВЫБРАТЬ НачалоДуги, НачалоДуги ИЗ ИсходноеОтношение
| ОБЪЕДИНИТЬ ВЫБРАТЬ КонецДуги, КонецДуги ИЗ ИсходноеОтношение;»;
Рефрен = «ВЫБРАТЬ РАЗЛИЧНЫЕ ПерваяДуга.НачалоДуги, ВтораяДуга.КонецДуги ПОМЕСТИТЬ ЗамыканияДлины#2 ИЗ ЗамыканияДлины#1 КАК ПерваяДуга
| СОЕДИНЕНИЕ ЗамыканияДлины#1 КАК ВтораяДуга ПО ПерваяДуга.КонецДуги = ВтораяДуга.НачалоДуги;
| УНИЧТОЖИТЬ ЗамыканияДлины#1;»;
Эпилог = «ВЫБРАТЬ КОЛИЧЕСТВО(НачалоДуги) Уровень, КонецДуги Элемент ПОМЕСТИТЬ ТаблицаУровней ИЗ ЗамыканияДлины#2 СГРУППИРОВАТЬ ПО КонецДуги;
| ВЫБРАТЬ ИсходноеОтношение.НачалоДуги Предок, ИсходноеОтношение.КонецДуги Потомок, ИсходноеОтношение.Ссылка Спецификация ИЗ ИсходноеОтношение
| СОЕДИНЕНИЕ ТаблицаУровней КАК Начало ПО ИсходноеОтношение.НачалоДуги = Начало.Элемент
| СОЕДИНЕНИЕ ТаблицаУровней КАК Конец ПО ИсходноеОтношение.КонецДуги = Конец.Элемент
| ГДЕ Начало.Уровень = Конец.Уровень»;
Запрос = Новый Запрос(Пролог);
МаксимальнаяДлинаЗамыканий = 1;
Пока МаксимальнаяДлинаЗамыканий < МаксимальнаяДлинаПути Цикл
Запрос.Текст = Запрос.Текст + СтрЗаменить(СтрЗаменить(Рефрен, «#1», Формат(МаксимальнаяДлинаЗамыканий, «ЧГ=0»)), «#2», Формат(2 * МаксимальнаяДлинаЗамыканий, «ЧГ=0»));
МаксимальнаяДлинаЗамыканий = 2 * МаксимальнаяДлинаЗамыканий
КонецЦикла;
Запрос.Текст = Запрос.Текст + СтрЗаменить(Эпилог, «#2», Формат(МаксимальнаяДлинаЗамыканий, «ЧГ=0»));
Возврат Запрос.Выполнить().Выгрузить()
КонецФункции
Запрос приведен на примере проверки зацикливания спецификаций продукции для типовой конфигурации «1С: Управление производственным предприятием». Приведенная функция выдает список связей входов и выходов спецификаций, находящихся в цикле вместе с указанием самих спецификаций. Очевидно, что ошибочной (приводящей к зацикливанию) будет, вероятнее всего, только одна из указанных связей. Можно не ограничиваться только сборочными спецификациями. Но следует учесть, что в этом общем случае могут существовать и правильные циклы сборки-разборки, которые запрос также будет показывать.
Понятно, что такой подход будет обнаруживать циклы любой длины. Безошибочно ограничивать максимальную длину пути можно количеством активных спецификаций, как и сделано в прилагаемой обработке.
Для примера приведен набор спецификаций, содержащий циклы. Это спецификация структуры всем известной детской песни «Вместе весело шагать»
«Песенка» получается по двум спецификациям: С1(Словечко1 + Словечко2) и С2(Куплет1 + Куплет2 + Куплет3).
Функция, обнаруживающая циклы, вернет следующую таблицу
5. Определение множества взаимозаменяемых позиций (аналогов) номенклатуры.
Существует несколько решений для хранения информации о взаимозаменяемости номенклатурных позиций [//infostart.ru/public/128065/]. Например, в УПП для этого используется регистр сведений «АналогиНоменклатуры», в записях которого указывается собственно номенклатура и заменяющий ее аналог (назначение других полей этих записей для данного обсуждения не существенно). Чаще всего можно считать, что если для детали “А” аналогом является деталь “Б”, то верно и обратное: для детали “Б” аналогом будет деталь “А”. Кроме того, если деталь “Б” является аналогом детали “А”, а деталь “В” является аналогом детали “Б”, то деталь “В” также будет аналогом детали “А”.
Как же следует задавать аналоги: по цепочке “А”->”Б”, ”Б”->”В” или звездой “А”->“Б”, ”А”->“В”? — Предлагаемый метод позволяет не задумываться об этом. В любом случае будут найдены все аналоги каждой номенклатуры. Для этого используется функция
Функция ТранзитивноеЗамыканиеАналогии(МаксимальнаяДлинаПути) Экспорт
Пролог = «ВЫБРАТЬ Номенклатура КАК НачалоДуги, Аналог КАК КонецДуги ПОМЕСТИТЬ ЗамыканияДлины1 ИЗ РегистрСведений.АналогиНоменклатуры
| ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ Аналог, Номенклатура ИЗ РегистрСведений.АналогиНоменклатуры
| ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ Номенклатура, Номенклатура ИЗ РегистрСведений.АналогиНоменклатуры;»;
Рефрен = «ВЫБРАТЬ РАЗЛИЧНЫЕ ПерваяДуга.НачалоДуги, ВтораяДуга.КонецДуги ПОМЕСТИТЬ ЗамыканияДлины#2 ИЗ ЗамыканияДлины#1 КАК ПерваяДуга
| ВНУТРЕННЕЕ СОЕДИНЕНИЕ ЗамыканияДлины#1 КАК ВтораяДуга ПО ПерваяДуга.КонецДуги = ВтораяДуга.НачалоДуги;
| УНИЧТОЖИТЬ ЗамыканияДлины#1;»;
Эпилог = «ВЫБРАТЬ НачалоДуги Предок, КонецДуги Потомок ИЗ ЗамыканияДлины#2 ГДЕ НачалоДуги <> КонецДуги»;
Запрос = Новый Запрос(Пролог);
МаксимальнаяДлинаЗамыканий = 1;
Пока МаксимальнаяДлинаЗамыканий < МаксимальнаяДлинаПути Цикл
Запрос.Текст = Запрос.Текст + СтрЗаменить(СтрЗаменить(Рефрен, «#1», Формат(МаксимальнаяДлинаЗамыканий, «ЧГ=0»)), «#2», Формат(2 * МаксимальнаяДлинаЗамыканий, «ЧГ=0»));
МаксимальнаяДлинаЗамыканий = 2 * МаксимальнаяДлинаЗамыканий
КонецЦикла;
Запрос.Текст = Запрос.Текст + СтрЗаменить(Эпилог, «#2», Формат(МаксимальнаяДлинаЗамыканий, «ЧГ=0»));
Возврат Запрос.Выполнить().Выгрузить()
КонецФункции
Используя данный запрос, можно существенно сэкономить на хранении информации об аналогах. То есть, вместо указания для каждой номенклатуры всех ее аналогов, можно указать основную позицию и ее заменители «звездой», либо задать аналоги по цепочке, либо пользуясь комбинацией этих подходов – в виде дерева. В результате будет храниться только «остов» отношения аналогии. Например, если в группе взаимозаменяемости 100 деталей, то по максимуму потребуется ввести 100х99 = 9900 записей типа номенклатура-аналог, а в случае хранения только основных записей потребуется хранить всего 99 записей!
На следующем рисунке приведены примеры аналогов. Красным обозначены связи, которые не хранятся в БД.
В УПП понадобится 5 записей регистра сведений «Аналоги номенклатуры» для этих связей.
После транзитивного замыкания аналогии будет сформирована полная таблица аналогов
Из-за фундаментального характера затрагиваемых понятий приведенные примеры, скорее всего, не исчерпывают всего списка применений предложенного приема. Надеюсь, приведенные решения являются достаточно поучительными и послужат хорошей основой для решения и других подобных практических задач.
Еще двум интереснейшим задачам-примерам будет посвящена отдельная статья.
ildarovich, спасибо за статью.
Но вот я сижу, и никак не могу представить разузлование спецификации номенклатуры этим методом. Слишком много требуемых параметров при расчете конечного расхода, не буду все перечислять, т.к. это можно увидеть в соответствующей функции в УПП например. Да, если бы нам просто требовались связи, возможно. Но нам требуются не связи, а конечный факт расчета, списания и получения.
По поводу аналогов, они требуются для работы обычного персонала, а не математиков аналитиков. Да и интерфейс последовательной связи аналогов будет весьма замудреным. Покажите мне Вашим способом запрос, который получит конечный расход аналогов по спецификации, с учетом приоритетов, остатков аналогов в НЗП по подразделениям, всех назначенных особенностей использования (с учетом пустых значений, которые «для всех»), расчета с учетом коэффициента и единиц измерения. Не забудьте, что остатки должны списываться последовательно до нуля с учетом приоритетов. У меня например получился один пакетный запрос из 12ти запросов в 800 строк, и это, естественно, с учетом работы типовой функции разузлования спецификаций, и это только для РА, а потом пришлось создавать аналог для ПУ для 4х запросов и связывать вставки кусков с комментариями в первом запросе. Если представить весь алгоритм полностью, натянутым на ваш механизм, то пошло оно все нафиг 🙂 Я и так от этого контура ползаю крабом по столу. Рекурсия куда проще и понятней, я пожалел, что вообще взялся тогда решать эту задачу одним запросом. Не даром этот модуль 1С до сих пор оставляет в полуавтоматическом режиме (распределение аналогов при распределении материалов в спецификации).
Это я тут наумничал к тому, что пока не будет нормальных хранимых процедур делать подобные вещи совершенно дико. Собрать текст запроса по кусочкам в случае работы с иерархией выглядит понятней, проще, а значит удобней для редактирования. Поясню, это лишь моё мнение.
Вот, Вот оно! Автор, молодец=) Интересная статья, интересное решение, полезные идеи/знания. А то уже как то надоели статьи о банальных вещах. Заметно, что задача проработана на «низком» уровне, начиная от реализации хранения данных в СУБД и применения к 1С.
Больше качественного и полезного материала!
Хорошая статья, возьму на вооружение
Взял на заметку! буду использоват…
(1) Разузлование спецификаций номенклатуры этим методом НЕ ПОЛУЧИТСЯ в принципе. Дело в том, что в расчете разузлования потребуется АРИФМЕТИЧЕСКОЕ сложение, а не ЛОГИЧЕСКОЕ, а значит, в формуле появятся биноминальные коэффициенты, которые будут «портить» расчет количества. Один и тот же путь вхождения будет учтен несколько раз в зависимости от его длины. Тут уже был был спор с моим участием, что лучше — запрос или рекурсия в расчете разузлования. Был ранее и остаюсь сейчас сторонником текущего решения в УПП, то есть рекурсии. Тут я на той же стороне, что и Вы и нам спорить не о чем. Почему Вы решили, что речь идет о разузловании? Возможно, Вас смутили слова «определять входимость деталей в узлы и готовые изделия по их спецификациям». Здесь я понимал под входимостью (смотрел в словаре) факт вхождения (да или нет), а не количество деталей в узле. Наверное, нужно было выразиться определеннее.
По-поводу аналогов. Пользователя не нужно пугать терминами. Ему нужно дать возможность вводить информацию в любом удобном и компактном виде, а перед использованием (или проверкой) разворачивать все аналоги, в том числе выводимые из основных. Второе, более тонкое, соображение, касается автосписания по спецификациям с учетом аналогов, приоритета, наличия и прочее. Неоднократно приходилось спорить с пользователями, которые ОШИБОЧНО считают автосписание частью автоматизации, потому что сам СЧИТАЮ АВТОСПИСАНИЕ ЗЛОМ. Вместо учета нас заставляют моделировать действительность. Обратная связь — основа управления — разрывается! Мы перестаем знать, что НА САМОМ ДЕЛЕ пошло на выпуск. Однако эти рассуждения уже не по теме данной статьи.
ildarovich, ну если Вы согласны со мной, что в реальности выбранные примеры прикладного использования Вашей методологии не корректны, тогда согласитесь: примеры выбраны не удачно, или методика использоваться будет крайне редко.
По поводу автосписания, действительно долгий и не нужный спор. Я автоматизирую крупные предприятия, где «расставлять флажки» списания аналогов не реально в принципе. Тысячи наименований аналогов и их текущие разбитые остатки в НЗП. Поэтому делается одним этапом в конце периода подводя под инвентаризацию. Здесь без автосписания аналогов по исходным комплектующим из спецификаций никак в принципе. Ваши же размышления более походят на теоретические, хотя, безусловно, доля смысла в них есть.
(6) Совсем не хочу соглашаться с тем, что
Во-первых, предлагается не методология и не методика, а прием. Примеров (пока) шесть:
1) определение всех предков всех потомков всех элементов(транзитивное замыкание) — написать первоначальный запрос и статью меня побудили два вопроса в форуме — значит, этот вопрос интересен, а решения никто кроме меня не предложил;
2) определение уровней всех элементов справочника — есть статья, где тот же вопрос решается гораздо более громоздким способом;
3) максимальная глубина справочника — долгий цикл или громоздкий метод на Наше1С (автор, видимо, задачу не из пальца высосал);
4) прародители — на мисте штук шесть веток про решение через ИТОГИ В ИЕРАРХИИ — там, что, одни теоретики собрались?
5) циклы в спецификациях — две статьи на Инфостарте. В одной — запрос в цикле, в другой процедура до 10 уровней максимум. Откуда взята проблема? Не из практики?
6) аналоги — посмотрите внимательно рисунок в статье: 5 записей в регистре и 14 в результате. Это реальная задача.
ildarovich, не люблю я спорить. Но уж ладно, поспорю:
1,2,3,4,5 Изначально требовалось написать один пакетный запрос, без дополнительных построений. Так что Вы задачу не решили. Конструировать запрос средствами языка можно для любой из этих задач, причем более эффективно, чем у Вас. Для каждой конкретной задачи имеется ввиду.
6. Изначально задача туповатая. Имея лишь справочник номенклатура, невозможно пользоваться всеми преимуществами аналогового учета, как например в УПП. Просто человек не в теме.
Некоторые на нашей памяти прикручивали ООП к 1С помница. Да, достоинств много, но нафиг никому не нужно.
Решение у Вас красивое, умно выглядит. Но на практике для меня например совершенно не применимо.
Больше не буду спорить, сорри 🙂
автору большое спасибо за
1)Решение важных народохозяйственных задач 🙂
2)интеллектуальное удовольствие.
Извини, но не понял пункт 4 с песенкой. Можно ткнуть пальцем, где там цикл ? я цикла не вижу. Куплет не состоит из песенок.
А по теме публикации — считаю, очень полезно. Именно потому, что древовидные запросы (задача, которая в СУБД Oracle решается штатно, т.к. в синтаксисе SQL-запросов предусмотрена специальная фраза для этого) — в 1С каждый строит сам, изобретая велосипед. Поставил бы еще плюсик-другой, если бы можно было.
Браво!=)
(14) В каждом из трех куплетов есть слова «раз словечко, два словечко — будет песенка«. Если в этом месте вместо произнесения слова «песенка» начинать то, что она обозначает, то есть саму песенку, то дети «зациклятся».
(0)
Сергей (ildarovich).
Очень хорошая и интересная статья. Как и все другие Ваши статьи по теме «Делаем запросом». Однако ни одна из них не может иметь практического применения т.к. не учитываются «особенности» многопользовательского и «интерактивного» доступа к информации.
Ну, например, какой смысл в «заранее определить «высоту» (число этажей) отображения списка»(с), если к моменту его реального отображения «высота» может измениться?
Или какой смысл в «определение циклов» на определенный момент состояния БД, если с общей информацией работают (исправляют/плодят циклы) несколько пользователей?
Для решения подобных задач и предложены в 1С способы (инструмент) работы с т.н. «объектной моделью» с привлечением инструментов блокировки отдельных записей/объектов. Увы, медленно работающие т.к. реализуются, опять таки, запросами. И с очень неэффективным выполнением элементарных машинных команд обработки информации в оперативной памяти.
Я понимаю, что в среде 1С-а приходится подбирать алгоритмы под схему базы данных, а не наоборот (как этого требует суть самой идеи БнД) — разрабатывать схему базы данных под постановку изначальной задачи. Но, думаю, даже в этих «узких» рамках можно и нужно разрабатывать алгоритмы имеющие возможность практического применение.
P.S. Поставил «плюс» под публикацию и (1) сообщение.
(35) И еще относительно параллелизма и блокировок. «Рассуждая на пальцах», можно представить себе любое сочетание событий. Более точным будет произвести количественную оценку. Это можно сделать с использованием моделей систем массового обслуживания (СМО). Если рабочий процесс 1 (кластера нет), то модель будет относиться к классу N/M/1. Здесь N – число пользователей, M – максимальная длина очереди (могу напутать что-то в обозначениях, заранее извиняюсь). Зная время выполнения запроса и число пользователей, максимальное время ожидания блокировки, Вы по любому учебнику по СМО определите «вероятность отказа в обслуживании». Подставив в него РЕАЛЬНОЕ время выполнения «моего» запроса, РЕАЛЬНУЮ интенсивность внесения изменений в соответствующий справочник (ну не автоматами же они генерируются), РЕАЛЬНОЕ количество одновременно работающих пользователей, Вы получите число со многими нулями – очень малую вероятность. Это ведь НСИ, она по определению меняется нечасто. Выпуск каждого нового вида продукции или изменения технологии – это целое дело (плавали, знаем). Возьмите из журнала регистрации РЕАЛЬНЫЕ данные по интенсивности работы со справочником спецификаций, контрагентов, номенклатуры для обычного предприятия! Поэтому меня больше беспокоит непонятность приема, его ненужность, большое время выполнения запроса в задачах с «неудобными» данными, а это выявит практика.
(37) Вы можете написать конкретную задачу просто и ясно без философии, где бы применялся этот метод. Только не теоретическую из головы, а конкретную задачу учета? Просто: дано это это, получить то-то то-то, где ваш метод будет действительно эффективен. Может тогда я вам поверю.
(36)
«Получается, что решен ПРАКТИЧЕСКИЙ вопрос.»(с)
Сергей (ildarovich).
Нет. Предложен способ выхода из угла, в который сами себя загнали. 🙁
В рамках 1С-возможностей совершенно реально и логичнее обеспечить, например, проверку «циклов» в процессе ввода информации. Равно как и обеспечить «высоту» для последующего её получения ОДНИМ обращение к БД. И для решения этих задач совершенно не требуется каждый раз перебирать «всю» информацию.
Я совершенно «не против» красивых/эффективных алгоритмов обработки информации запросами. Но всему свое место. Запрос для пакетной обработки информации (отчетов). Прямой доступ для «интерактивных» многопользовательских систем. Разные задачи (цели). Разные способы (алгоритмы) решения. Разные инструменты. Разные схемы БД (даже в рамках реляционной модели!!!).
Т.е. я об этом написал в (35) сообщении…
P.S. Соглашусь с Вашим отнесением подобных решений к практическим, если Вы дадите описание алгоритма поиска «циклов» и ИСПРАВЛЕНИЯ «циклов» запросами в многопользовательской среде. И так, чтобы эта задача не превращалась в подобие перфокарточной/пакетной технологии середины прошлого века.
(38) Могу привести, например, такой случай из практики: В начале июля этого года у нашего клиента — крупной торговой компании перестал проходить обмен УТ->БП (свои правила обмена через прямое подключение). Очень большие базы, сильно переписанная УТ10.3, автоматизированы все обмены: с импортной WMS, производством, таможней, поставщиками, филиалами и контрагентами, работа 24х7. Естественно, кластер серверов, MSSQL2008, система хранения. Разбирались 1,5 дня, задержали отчет иностранному владельцу, был скандал. Выяснилось: зацикливание справочника номенклатура! (Можете почитать: «Что бывает, когда родитель ушел в себя») Саму первопричину не нашли, ситуация никак не воспроизвелась — сейчас платформа не позволяет этого делать в принципе. Понятно, что виновато какие-то сочетание событий в обменах и в СУБД. Тем не менее для подстраховки теперь можем применять контроль зацикливания. Может быть, это не совсем такой пример, которого Вы просили, но
Растут стихи, не ведая стыда
(40) не понял, вы использовали этот механизм при обмене? Или просто для контроля зацикливания?
(40)
и причем тут ваш метод, где допущена обычная для 1с-ника ошибка?
Почему обычная? Потому что 1С «настраивает» всех своих программистов не на ПРЕДУПРЕЖДЕНИЕ и ПОИСК ошибок на этапе разработки, а исправление по факту по принципу: «Если ничего не случилось — ну и ладно».
А по-простому — «авось, пронесет».
да, в 1С сделано все, чтобы было как можно запутаннее, не говоря уже об отсутствии инструментов моделирования работы обработокобменов, контроля исполнения кода и редактирования понаделанного и понаписанного.
(41) Да, в этом случае, просто для контроля зацикливания
(42) Уважаемый AlexO! В мире нет ничего идеального. В том числе нет идеальных программ. Например, в программном обеспечении лунной миссии Appolon после первого запуска было найдено более 100 ошибок. 1С не хуже и не лучше других платформ. Это нормальный эволюционирующий в нужную сторону инструмент для своего класса задач. Прошу понять: для меня разговор 1С — плохая — бессмысленный разговор, он ни к чему не ведет, ни к чему не побуждает. К тому же мне неудобно за Вас: получается, что за глаза Вы обижаете людей (разработчиков платформы), которых я безусловно уважаю. В программном коде платформы 8.3 около 10 миллионов строк на С++. И это (здесь я имею ввиду все версии платформы) нормально работает! Более чем у миллиона пользователей! На нескольких программных платформах, в том числе мобильных iOs, Android, с несколькими СУБД, с отказоустойчивостью и в облаках тоже.
А мой метод (прием) при том, что он позволяет , например, в этом конкретном случае обнаружить ошибки, появившиеся в результате программного сбоя (у этого термина есть точное определение), сэкономить время и нервы большого количества людей.
(44)
так есть несколько способов сделать это, окромя вашего метода.
и их не исправили, как это делают в 1С?
оно может на выставочных стендах и нормально работает (хотя вот у Microsoft виснет и на стендах).
А вот в реальных базах в конце концов все тухнет и падает, хоть в 8.1, хоть в 8.2, и 8.3 — что, выделяется из этого ряда?
я вот … не могу уж очень сильно уважать РАЗРАБОТЧИКОВ ЯП, в котором сделано многое для запутывания разработки и усложнения интерфейса между этим самым ЯП и разработками, которые он пытается усиленно пользовать.
(45)
— опишите, пожалуйста, эти способы. Я постараюсь показать выгодные отличия.
— приведите, пожалуйста, конкретные примеры. Желательны не общие слова, а подробные технические детали.
(46)
так мы еще и в старой теме обсуждали варианты поиска родителей…
чем ваш-то метод отличается? В какую сторону? Результат-то то же 🙂
(47) Поиск родителей и контроль зацикливания — это СОВЕРШЕННО разные задачи. С еще большим интересом жду ответа на вопрос
(49) Смотрите, предлагается ПРИЕМ: В запросе некоторая исходная таблица соединяется сама с собой. Потом результат снова соединяется сам с собой, потом этот результат снова соединяется сам с собой и так далее. Если в исходной таблице были пути длины один, то после 10-го соединения так получатся пути длины тысяча (два в десятой степени). Если в исходной таблице в одной строке были непосредственно знакомые с друг другом люди, то во второй таблице — знакомые знакомого, в третьей — знакомые — знакомого знакомого знакомого (именно четыре!). А в десятой Вы будете рядом, даже если Вы были знакомы через тысячу посредников. Эта последняя таблица называется таблицей транзитивного замыкания.
Ее БЫСТРЫЙ способ получения позволяет решить несколько совершенно РАЗЛИЧНЫХ практических задач (об этом вторая статья).
Например, определить всех опосредствованных родителей любого элемента справочника. А раз всех, значит, первоначальных, я их назвал прародителями (родоначальники). Это можно сделать и другим способом, как Вы правильно заметили, но НЕ В ПАКЕТНОМ ЗАПРОСЕ и НЕ ТАК БЫСТРО как «моим» способом.
На базе той же таблицы можно НАЙТИ ЦИКЛЫ — это совершенно другая задача. Из другой оперы, так сказать. Для этого сначала считается уровень каждого элемента. Под уровнем понимается число записей, в которых элемент справа. Далее находятся записи в исходной таблице, уровень элементов в которых одинаков. Не очевидно, но такие записи образуют цикл! А обработка их супербыстро находит!
Поэтому очень Вас еще раз прошу ответить, какие способы Вы имели в виду, говоря
(49) Статья называется
Про это там и написано. Про поиск циклов — в пункте 4. Вместе СО ВСЕМ ИСХОДНЫМ КОДОМ. Больше никакого кода не требуется! Код нарочно развернут, снабжен отступами и самодокументирующими названиями переменных. Он должен быть понятен. Приведен рисунок и скриншот обработки после вызова метода для спецификаций рисунка. В комментариях к первой статье я привел получающийся текст развернутого запроса. Привел работающую обработку для того, чтобы можно было проверить метод на своих данных. Что еще я мог бы написать про этот метод, чтобы быть понятым?
(51)
тогда чем код в п.4 отличается от кода поиска родителей из «Транзитивного запроса»?
(52) Он отличается «прологом» и «эпилогом». Пролог отличается тем, что формируется еще одна временная таблица, запоминающая исходное отношение. Кроме того, само отношение строится из входов и выходов активных спецификаций. Эпилог отличается тем, что рассчитываются уровни, а затем исходная таблица соединяется с двумя таблицами уровней для левого и правого элемента и «режется» по условию равенства уровней. Все снабжено описанием и это прямо и ясно написано в коде.
(40) пример конечно явно надуманный. В любом случае, имея сбой на уровне платформы вы обязаны избавиться от него в первую очередь, а не встраивать сервисные механизмы в горлышко. (Не знаю, что у вас там за канал). AlexO, уже написал всё. Избавится можно как на уровне платформы, так и на программном уровне, 1С отрабатывает зацикливание иерархии. Оставляя ошибку вы наживаете себе серьезный геморрой, т.е. лечите симптом.
Я понимаю, вы бы еще привели пример с зацикливанием реквизитов, более реалистичный. Но опять же, легко решается кстати без выстраивания всей цепочки.
Ваши разработки могут служить лишь для собирания статистики в запущенных случаях. Кому она нужна, вопрос.
P.S. Я вспомнил один подобный случай, еще на 8.0 давно очень. Обмен явно издевался и выстраивал перепендикулярный гемор, как раз по связи владелец-реквизит. В конфигурации был очень хитроперевернуто решен механизм комплектации, который по сути повторял 7.7. того-же творца. Так вот, паренек, который на тот момент работал с 1С меньше года решил эту проблему на SQL, и за что был анально покаран. После этого, он написал триггер, который пузырьковал эту штуку. Я его реально зауважал. Вот, стремитесь. Это всё не сложно, логично и выглядит действительно круто.
(53)
т.е. поиск
из примера ( 49) решается исключительно данным способом?
Или — зачем такие сложности, Сергей, для решения вышеуказанной задачи? 🙂
(54) Слово «надуманный» здесь никак не подходит. Это АБСОЛЮТНО РЕАЛЬНЫЙ случай. Чтобы локализовать эту проблему, требуются диагностические инструменты. Этот запрос — он и есть(еще раз повторю, это один из примеров его применения). Мы не можем позволить себе контролировать каждую запись в справочник на зацикливание, перепроверяя платформу. Это скажется на быстродействии. Поэтому контроль именно в том месте, где нужно — перед проблемной операцией. С тех пор еще много чего в профилактических целях делали, сбой не повторялся.
Пример с зацикливанием реквизитов я привел в основной статье в пункте 4. Спасибо, что сочли его реалистичным.
(54) AlexProg,
так обкатанные методы обработки данных не отменены нигде, кроме 1С 🙂
(55) Сложность в том, что число уровней в «моем» методе ничем не ограничено. Он решает и задачу тогда когда Вам потребуется сделать ТЫСЯЧУ СОЕДИНЕНИЙ, проверяя
, а мне всего 10. В общем, попробуйте.
(54) Если Вы все же захотите действительно разобраться в сути этого приема, то Вы поймете, что он также будет многократно ускорять и решение подобных задач на чистом SQL.
— попробуйте ка доказать это утверждение! — Очень хочется посмотреть!
По прежнему жду от AlexO подтверждения того, что
(59) Решение реквизитов лично я делал кучей способов.
Например движение из двух точек к центру быстрей, чем движение от А до Б метода цепочки.
И кстати, всегда метод пересечения подмножеств, будет быстрей цепочки, если нужно собрать всю статистику разом, даже при количестве записей более миллиона.
И еще я бы рассмотрел анализ прямой вложенности запроса, при условии, что она работает на 10% быстрее, в случае длинных связей большой прирост производительности. Другой вопрос, где оно обломается, я думаю вы уже обсуждали этот вопрос.
Но это философия. Если правильно архитектурно подходить, то необходимо создавать систему контроля при вводе данных и где не критична скорость, потому что проконтролировать использование таких данных, вы можете только тогда, когда сами продолжаете разработку. А это в наше время непозволительная роскошь. А вас потом вспомнят «добрым словом».
(37)
Сергей (ildarovich).
Очень огорчил меня этот Ваш текст.
Попробую привести пример:
Имеем в базе данных иерархическую структура. Не имеет значение её прикладное назначение и «интенсивность внесения изменений»(с). По определению «задачи» в ней могут быть «циклы». Их надо искать и исправлять. Запускаем Ваш алгоритм. Получаем информацию о нескольких «циклах». Хотим исправить ошибки побыстрей. Сажаем двух операторов. И они начинают исправлять ошибки. Допустим, что самым простейшим способом — удалением «плохих» узлов. Смотрят, грубо говоря, каждый в свою «распечатку». Какова вероятность того, что один из операторов начнет исправлять уже исправленный «цикл» другим оператором? Т.е. им надо обеспечить постоянное обновление «распечатки» с учетом изменений после каждой процедуры исправления? Каким алгоритмом это следует делать? Как учесть в Вашем алгоритме тот факт, что в момент его выполнения под нужды одного оператора, другой оператор уже «прицелился» к очередному «циклу» и «нажимает» кнопку удаления?
(60)
Убедительно Вас прошу, приведите все, какие вспомните из этой «кучи»! Пока это только слова. Тогда я Вам смогу нагляднее показать, в чем практическая выгода «моего» способа.
(61) «Замечено, что ОДНОВРЕМЕННОЕ нажатие четырех кнопок на клавиатуре дает более интересные эффекты, чем одновременное нажатие трех» (шутка)
— просто не делайте этого! (организационное решение) «Поспешишь — людей насмешишь» (народная мудрость). Если Вам нужно организовать работу в коллективе — первым делом нужно «распределить работу». А давать задачу «на собачку-драчку» — кто быстрее найдет ошибку — это для игры, а не для серьезных дел, тем более, исправления ошибок! Кроме того, есть механизм управляемых блокировок. (программно-техническое решение). Если же у Вас специализированная система по «разрыву циклов» с большим потоком входящих требований (пока не вижу реальной задачи для такой системы) следует добавить дополнительные таблицы (справочники, регистры сведений), разбить граф на кластеры, назначить ответственных, придумать систему диспетчиризации.
(1) Не хотел отвлекаться от темы, но все же было бы интересно взглянуть на Ваш запрос из 800 строк. Вы можете оформить его как обработку заполнения табличной части «Распределения материалов на выпуск». Штатный механизм не учитывает аналогов, так что будет интересно. Дело в том, что я тоже решал эту задачу. В моем запросе строк было ГОРАЗДО меньше. Правда, с темой данной статьи это не связано.
(65) Опять одни слова — ни строчки кода. Значит, той разработки тоже нет. Предлагаю отложить все разговоры до того, когда Вам будет что предъявлять в доказательство своих пока безосновательных умозаключений. Пока из последовательности Ваших мыслей-слов сделал вывод, что в сути моего метода Вы не разобрались, а жаль.
(67)
— это означает, что Вы представите все таки когда-нибудь наконец какие-либо доказательства?
— зря Вы так, соревноваться вообще полезно. Впрочем, я тоже иногда повторяю за рулем:
Пока Ваше решение — это еще совсем не решение. Его никто не видит. Но действительно интересно будет посмотреть. Матчасть учу постоянно, скрипеть зубами привычки нет.
(63)
«просто не делайте этого! (организационное решение)»(с)
Сергей (ildarovich).
Эта шутка мне больше понравилась, чем про три клавиши. 🙂
Еще раз на пальцах.
Запрос — это пакетная обработка информации.
Пакетная обработка имеет ряд «характеристик» (упрощенно говоря!!!):
1) Скорость выполнения.
2) Количество информации «вовлеченной» в получение результата.
3) Время актуальности конечного результата.
Вы сосредоточились ТОЛЬКО на первом пункте.
Но по сути ВСЕХ Ваших решений требуется выполнять «интерактивную» обработку информации на основании результатов «пакетной» обработки. Чем больше информации «вовлечено» в получение результата пакетом, тем выше вероятность потери её актуальности. И меньше времени она является актуальной.
Для решения этой «проблемы» применяются блокировки. В Ваших алгоритмах требуется блокировка ВСЕЙ таблицы на ВСЁ время выполнения «пакета» и последующего воздействия на информацию в интерактивном режиме. Т.е. — однопользовательский режим обработки информации. И уменьшение времени выполнения «пакета» — это только «Игры разума»(с).
Сергей. Это элементарные, очевидные вещи. Если не смотреть на задачи «узко-конкретно»(с).
Я очень огорчен. Желаю успехов…
(69)Владимир! – Меньше всего хочу, чтобы Вы огорчались. Знаете, как на флоте борются с тараканами? – Дают им напиться рассола, а потом, когда они захотят пить и вылезут из трюма, задраивают все люки. И НЕВАЖНО – УТОНУТ ОНИ ИЛИ ПОДОРВУТСЯ НА МИНАХ! Так и в моем случае – я придумал прием и написал на его основе несколько запросов для решения известных всем задач. А будут ли исправляться найденные ошибки или списываться найденные аналоги при этом неважно. Вспомните про тараканов – может быть, по ним ударит ракетный крейсер! Статья называется не «разработка высоконагруженной системы для контроля и исправления ошибок зацикливания в ориентированных графах большой размерности». Она называется «Уровни, глубина, прародители, циклы и аналоги ЗАПРОСОМ» и речь идет о ЗАПРОСЕ. Вы сами сузили метод до контроля циклов, присоединили задачу исправления ошибок, надумали ситуацию высокой интенсивности и параллелизма исправления ошибок и огорчились.
Далее, при чем здесь платформа 1С? Прием с тем же успехом можно реализовать в виде RCTE на SQL. Возможно, если бы sql.ru был бы таким же «нарядным» как Инфостарт, я бы предлагал прием там.
Так что не огорчайтесь! Смотрите: весна, все цветет, солнце, птички поют, кенгуру прыгают! – Радуйтесь! Вы скажете – при чем здесь Австралия! Вы притянули за уши надуманную задачу и расстроились. Я присоединил за уши Австралию и обрадовался! Чувствуете разницу? Так что больше позитива – больше креатива! Успехов и вам!
(ildarovich) Есть такая категория людей, которым очень нужно время от времени говорить кому-нибудь «ты тупой неудачник». И что бы ты ни написал, всё равно ты неудачник и лошара. Не обращай внимания.
Нормальные же люди взяли на заметку, и при случае применят без криков.
Очень интересно и полезно. В некоторых случаях может сэкономить кучу времени. Премного благодарен автору.
(71) ADirks, зато от таких людей есть очень хороший плюс — они постоянно побуждают тебя двигаться дальше, познавать, изучать, разрабатывать новые идеи 🙂
…
Запрос = Новый Запрос;
Запрос.Текст = «ВЫБРАТЬ
| 1 КАК Ссылка
|ИЗ
| Справочник.Номенклатура КАК Номенклатура
|ГДЕ
| Номенклатура.ЭтоГруппа = Истина
|ИТОГИ ПО
| Номенклатура.Родитель.Ссылка ИЕРАРХИЯ»;
РезультатЗапроса = Запрос.Выполнить();
Дерево = РезультатЗапроса.Выгрузить(ОбходРезультатаЗапроса.ПоГруппировкамСИерархией);
МаксимальныйУровень = 0;
МассивСтрок = Дерево.Строки.НайтиСтроки(Новый Структура(«Ссылка», NULL), Истина);
Для каждого СтрокаДерева Из МассивСтрок Цикл
Уровень = СтрокаДерева.Уровень();
Если МаксимальныйУровень < Уровень Тогда
МаксимальныйУровень = Уровень;
КонецЕсли;
КонецЦикла;
МаксимальныйУровень = МаксимальныйУровень + 1;
Прошу прощения за (74) (75). Нажимал «просмотр» — а вышел ответ…
Что собственно хотел сказать.
Возникла задача определить максимальный уровень группы справочника «Номенклатура», не важно есть в ней элементы, или нет.
В итоге вышло (75).
У нас в справочнике «Номенклатура» — ~200К элементов, ~3,5К групп.
На этих данных функция «ГлубинаИерархии» «работала» 2,5 минуты (база тестовая, файловая).
Предлагаемый мной вариант — меньше 5 секунд.
Т.е разница на порядок как минимум. Тестировать более строго пока нет возможности.
(76) Вообще я не претендую на то, что предложенный метод будет всегда и во всем лучшим. Его утяжеляет то, что он на промежуточных этапах строит все возможные связи в графе. А также то, что временные таблица не индексированы. Нужно, кстати, попробовать тут соединение на основе сортировки слиянием (то есть объединить и сгруппировать) — должно получаться быстрее. Но запрос будет более громоздким, а я здесь стремился к простоте и изяществу.
Но все же, чтобы соревнование было честным, Вам нужно поставить условие
Номенклатура.ЭтоГруппа = Истина
и в мой запрос тоже. В пролог.
Пролог = «ВЫБРАТЬ Родитель НачалоДуги, Ссылка КонецДуги ПОМЕСТИТЬ ЗамыканияДлины1 ИЗ Справочник.Номенклатура
| ГДЕ Родитель <> Значение(Справочник.Номенклатура.ПустаяСсылка) И Ссылка.ЭтоГруппа
| ОБЪЕДИНИТЬ ВЫБРАТЬ Ссылка, Ссылка ИЗ Справочник.Номенклатура;»;
Сообщите, пожалуйста, результат.
И еще, раз Вы заинтересовались, хотел еще раз подчеркнуть факт, что приведенные функции приведены как функции для примера. Я считал, что построенные запросы будут в виде текстов использоваться внутри других запросов. Чтобы их результаты могли обрабатываться дальше. Я как-то это недостаточно ясно выразил в статье, судя по стандартной реакции читателей.
(77) Тут и соревнования то никакого быть не может. В данной задаче работа с ссылками убивает все.
Я не собирался устраивать соревнования, только хотел поделиться придуманным мной велосипедом. На целую статью наверное маловато текста, а как комментарий/дополнение к вашей публикации — вполне.
Попробовал ваш вариант с ограничением по группам — значительно лучше, 20с
(78) ОК, понял. За результат спасибо, однако я допустил неточность, забыв про условие во второй половине запроса.
Пролог = «ВЫБРАТЬ Родитель НачалоДуги, Ссылка КонецДуги ПОМЕСТИТЬ ЗамыканияДлины1 ИЗ Справочник.Номенклатура
| ГДЕ Родитель <> Значение(Справочник.Номенклатура.ПустаяСсылка) И Ссылка.ЭтоГруппа
| ОБЪЕДИНИТЬ ВЫБРАТЬ Ссылка, Ссылка ИЗ Справочник.Номенклатура И Ссылка.ЭтоГруппа;»;
Также взглянул на Ваш код. Возможно, Вам понравится использовать конструкцию, которую использую в этом случае я:
Для каждого СтрокаДерева Из МассивСтрок Цикл
МаксимальныйУровень = МАКС(МаксимальныйУровень, СтрокаДерева.Уровень())
КонецЦикла;
(79) И это правильный ответ ) 7 секунд. Только скорее всего не И Ссылка.ЭтоГруппа, а ГДЕ Ссылка.ЭтоГруппа
Уточняю, (75) — 1 секунда.
Про решение через МАКС спс)
Не могу понять что за параметр «МаксимальнаяДлинаПути» в каждой функции? Откуда он берется?
(81) Это оценка максимальной длины пути в графе. Для отношения иерархии в справочнике длина пути никогда не может быть больше числа элементов, например. Но, как правило, можно определить это число «из соображений здравого смысла», установив, например, что в справочнике не будет больше 1000 уровней.
Обещанные в конце статьи «две интереснейших задачи-примеры» можно посмотреть в публикацииОпределение кратчайших путей, критических путей одним запросом .
Функция ГлубинаИерархии(ИмяСправочника, МаксимальнаяДлинаПути) как раз и вычисляет «МаксимальнаяДлинаПути». Тогда чему же равно значение «МаксимальнаяДлинаПути», передаваемое в эту функцию?
ЗЫ Не заметил, что ответ приведен двумя постами выше. Но задать МаксимальнаяДлинаПути = 1000 неразумно, т.к. столь большое значение снизит производительность функции. Получается, надо «на глазок» задавать достаточно-реалистичное значение этого параметра (слегка с запасом).
Интересная статья, даже почти разобрался 🙂
Тут, в примере:
можно определить последний уровень, а вот можно ли как-то получить номера уровней всех групп?
Например:
Предок Потомок Уровень
Группа1 | Элемент | 1
Группа2 | Элемент | 2
Группа3 | Элемент | 3
Группа4 | Элемент | 4
(85) Странный вопрос. Потому что код, который вами же приведен, делает именно то, что вы хотите — определяет номера уровней всех элементов (и групп, естественно тоже). Это раздел 1 статьи. Там и скриншот результата работы функции приведен, где видно, что определяются именно уровни.
Ну а если затруднения в том, чтобы отобрать группы, то посмотрите начало запроса в комментарии (79). Там в два места добавлена проверка Ссылка.ЭтоГруппа. Таким тогда и должен быть пролог запроса.
(86) может я что-то не понял (или не так написал) но, на мой взгляд:
приведенный код позволяет определить уровень группы в который входит элемент. Для моей задачи нужно знать уровень группы в который входит элемент, а также уровень группы в которую входит группа в которую входит элемент 🙂 и т. д.
(87) teriban, из первоначальной формулировки вашего вопроса было непонятно, но если теперь формулировка такая
то для этого нужно дополнительно из всех групп и элементов, уровни которых определены в примере 1, отобрать те, которые включают данный элемент, то есть который является их потомком.
…как-то так…
(0) Замечательные алгоритмы, за которые огромное спасибо.
Только вот требуется помощь. Долгое время уже кручу и так и сяк, не могу найти нормального решения. Может подмогнете?
Исходные данные:
Иерархический справочник с подчинением элементам. Глубина неизвестна (предположим до 10 уровней). Длина веток может быть любой.
Задача
Получить Список элементов справочника с заданным родителем. В выборку должны попасть только те элементы у которых хотя бы один потомок, находящийся на самом нижнем уровне удовлетворяет условию типа Элемент.Свойство=&МоеСвойство
(91) kentavr27, ну да, то, что свойство будет у самого нижнего уровня, здесь никак не проверяется. Нужна еще одна проверка для «конца дуги» при формировании таблицы «содержательная».
Показать
Код нужно еще проверить, но идея в том, что у «листьев» только одна исходящая дуга (замкнутая на себя)
(92) Да. То что нужно. Спасибо огромное. Осталось терь в голове это все упорядочить… 🙂
Для УПП 1.3.64.1 циклы не работают!!!
Вот пример иерархии в справочнике спецификаций и сама спецификация
(94) (95) DeniNikitin, уточните, пожалуйста, как именно не работают, будем разбираться:
— ошибка выполнения? — тогда скриншрт пришлите
— не находит циклы? — тогда желателен скриншот не одной спецификации, а всех, входящих в цикл.
(96)
Не заполняет вообще, скрин спецификаций из вашего запроса прикладываю или какой скрин вам нужен?
Ещё прикладываю файл excel со всем списком спецификаций из справочника!
(98) DeniNikitin, спасибо, попробую разобраться на следующей неделе, но вы уверены, что в этом наборе спецификаций есть циклы активных спецификаций?
(99)
По подробней как это проверить?
Если вы про то что изделие состоит из другиз изделий, то да! Например: Кисть состоит из бугеля и щетины, а бугель состоит из подшипника и ручки и.т.д.!
(101) DeniNikitin, назначение функции, приведенной в разделе 4 — определение ЦИКЛОВ в наборе активных спецификаций. Наличие циклов — это ОШИБКА, допущенная при вводе спецификаций. Эта ошибка приводит к зацикливанию расчетов потребностей в материалах в процессе разузлования или неточностям этих расчетов.
Цикл — это ситуация, когда
где в «и т.д.» есть спецификация, где ручка состоит из кисти (ошибочно), в результате образуется цикл через несколько спецификаций.
В большом наборе спецификаций обнаружить ошибку в виде цикла трудно. Решение из пункта 4 упрощает решение этой задачи. И если на вашем наборе данных обработка не находит циклов (ошибок), возможно, значит, что циклов (ошибок) в вашем наборе нет.
(102)
Тогда понятно, я просто думал, что это функция позволить мне вернуть таблицу значений разобранную и по ней уже работать, а так всё равно писать надо будет!
Спасибо за статьи!
Очень интересно, действительно впечатляет.
А насчет максимальной глубины справочника, я, конечно, чувствую себя жутким быдлокодером, но всегда использовал что-то типа такого решения:
ВЫБРАТЬ
КОЛИЧЕСТВО(РАЗЛИЧНЫЕ Номенклатура.Ссылка) КАК Ссылка
ИЗ
Справочник.Номенклатура КАК Номенклатура
ГДЕ
Номенклатура.Родитель…n….Родитель = ЗНАЧЕНИЕ(Справочник.Номенклатура.ПустаяСсылка)
Минус метода, что надо знать примерно возможную верхнюю границу количества родителей. Если с ней получаем 0, то затем количество родителей уменьшаем n на один до тех пор, пока результат запроса не станет равен 0.
Если значение верхней границы очень уж высоко, то делать вручную неудобно и тогда этот запрос пихаем в обработку, уменьшая n (количество родителей) в запросе программно.
Насчет быстродействия не знаю, но для моих задач всегда быстро срабатывало.
ildarovich,
спасибо огромное за статью)))
Большое спасибо!
(104)
ВЫБРАТЬ
КОЛИЧЕСТВО(РАЗЛИЧНЫЕ Номенклатура.Ссылка) КАК Ссылка
ИЗ
Справочник.Номенклатура КАК Номенклатура
ГДЕ
Номенклатура.Родитель…n….Родитель = ЗНАЧЕНИЕ(Справочник.Номенклатура.ПустаяСсылка)
извиняюсь за некропостинг, но такой метод можно ускорить с помощью половинного деления. Тогда в худшем случае потребуется не N раз выполнять запрос, а только log2n раз. (берем стартовое значение, например 10, если взяли мало, то умножаем на 2, если много, то делим пополам отрезок и т.д.)
(107) Сомневаюсь, что так можно добиться ускорения. Дело в том, что приведенный запрос, который вы хотите поместить внутрь цикла подбора значения уровня, сам по себе не быстрый. За счет соединения через точку Родитель…Родитель…Родитель для каждого элемента справочника выполняется n неявных соединений, где n — пробная глубина справочника.
В вашем предложении «если взяли мало, то умножаем на 2» означает, что запрос и все неявные соединения в нем выполняются много раз заново. Это очень расточительно. Поэтому метод, предложенный в статье, который решает ту же задачу с использованием в единственном пакетном запросе log2(n) соединений, вместо n соединений, будет работать быстрее.
Вообще выигрыш (при использовании подхода, изложенного в статье) будет чуть меньше n/log2(n), так как потребуется время на запись элементов временных таблиц.
(108)
На самом деле такой запрос очень быстрый, так как он транслируется в запрос SQL с соединениями по индексированному полю Ссылка. В базе с количеством элементов номенклатуры около 112 тысяч, запрос с 7-ю обращениями к родителю выполняется за 0,3 сек. Причем при количестве соединений больше максимальной глубины иерархии, время выполнения запроса почти не увеличивается. Можете сами попробовать.
Показать
Статья интересная. Однако моя попытка перепилить для документов в ЗУПе мне не удалась. В ЗУПе есть такой нюанс с иерархией Документов. Ряд документов имеют ссылку на предка в «ИсправляемыйДокумент» или в «ПерерассчитываемыйДокумент». Очень хочется получить иерархию исправлений и пересчетов от первого откорректирораванного документа, то есть от документа на который есть ссылка в реквизите
(110) А что конкретно не получилось?
Возможно, проблема в том, что первый откорректированный документ нужно искать среди вообще всех документов, то есть нет никакой возможности предварительно сузить область поиска за счет отбора по сотруднику, подразделению, периоду и тому подобное. Тогда документов будет больше нескольких тысяч и метод, на вход которого нужно подать связи всех документов в области поиска, будет тормозить.
То есть метод рассчитан все же на выявление длинных связей среди относительно небольшого количества объектов (до нескольких тысяч). И отличается тем, что в процессе работы устанавливает (заодно) все связи, даже те, которые впоследствии могут и не понадобиться.
Спасибо за публикацию.
Метод нашел ПРАКТИЧЕСКОЕ применение в реальной задаче — поиск настройки номенклатуры на ближайшем ее родителе.
Проблема оперирования со всей таблицей была решена усложнением пролога, сдесь он получает все группы таблицы и только нужные элементы.
Показать
В эпилоге получаются уровни группировок родителей, затем все это соединяется с регистром в котором хранятся настройки и группируются по МАКСИМУМ(Уровень).
В базе с 146тыс позиций номенклатуры и обработкой 1000 элементов с максимальной длиной 30 отрабатывает за менее 300мс.
P.S.: Писал запрос понятным для себя языком, т.к. не имею математического образования и все эти термины мне непонятны, поэтому стиль запроса немного отличается от того что в статье. Смысл при этом не поменялся. Также добавил поле СсылкаЭтоГруппа, т.к. в эпилоге накладываю на него отборы, через точку работает медленней на 50-100 мс.
P.P.S.: Попробовал выполнить оптимизацию запроса добавлением индексов во временные таблицы — ничего не добился, да и старался немного. SQL Profiler говорит о том, что основное время тратится на запись временной таблицы в tempdb. При этом каждая следующая таблица обрабатывает примерно за одинаковое время, т.е. увеличение максимальной длины будет добавлять в моем случае примерно 50мс к каждой новой таблице.
(112) Большое спасибо за информацию, такие отзывы (о практическом применении) очень ценны для меня.
По поводу записи временной таблицы — полностью согласен. Этот фактор обязательно нужно учитывать. Недавно закончил развитиеметода ускорения расчета нарастающего итога . Там оказалось, что учет времени записи элементов временной таблицы позволяет выбрать оптимальный размер группировок.
(111) Отобразилась таблица с пустыми ссылками по числу документов данного вида в БД. Я же изначально искал возможность получить деревце взаимных связей некоторых документов. Мне необходимо получить иерархию дерево причём странным образом. То есть теоретически мне для проведения и/или записи исправленных и/или перерасчитанных документов необходимо получить дерево связей. Причем получить его таким образом что бы я мог начать пробегать это дерево от листьев. Мне необходимо восстанавливать для обработки последовательность, потом рвать связи ведущие от листьев на самого первого родителя по всей цепочке, потом корректировать движения и потом восстанавливать те связи которые до этого были от восстановления ссылки на самого первого родителя в документе который этого родителя исправляет.
Мне этот метод очень помогает. В мой практике приходится выполнять рекурсивную обработку древовидных справочников. Стандартные методы рекурсивного обхода не помогают, поскольку на определенном уровне рекурсии происходит переполнение стека и 1С прерывает выполнение алгоритма. Самым удобным оказалось выполнить разворот рекурсии обхода дерева в линейную последовательность и эмулировать стек вызовов на массивах. И вот тут на первое место выходит необходимость сформировать последовательность обхода дерева так как он обходился бы при рекурсивном обходе и поместить эту последовательность в стек обхода узлов. Так вот замыкания позволяют очень просто сформировать такую последовательность и в десятки раз увеличивают скорость выполнения алгоритма в целом, по сравнению с рекурсивным обходом, и по памяти дают приличный выигрыш.
Я оцениваю этот метод как «Сложный». Это значит, что далеко не любой программист может сходу разобраться в коде, активно опирающемся на данную методику, что сужает для заказчика выбор людей для сопровождения готового проекта.
Несмотря на «сложность», часто, применение этого метода бывает единственно возможный выходом. Например, когда у заказчика уже давно используются структуры данных, на которых работали рекурсивные алгоритмы, а теперь данные разрослись и нужно вернуть приемлемое время выполнения.
Поэтому я считаю, что статья очень хорошая, обсуждать такие приемы на уровне «мне это в практике никогда не требовалось и не потребуется» считаю бесполезным занятием. Умение применять такие алгоритмы позволяет на этапе проектирования решения задачи заказчика оценить практическую осуществимость, сложность задачи по времени реализации и прожорливость реализуемого алгоритма с точки зрения машинного времени и памяти.
Кроме того, для меня подобный навык оставил еще один плюс. Теперь при разговоре с заказчиком я легко выявляю места на которых спотыкаются обычные методы решения и легко помогают замыкания. Я могу заранее предупредить заказчика и последствиях принятия задачи в том виде, как он ее сформулировал и показать варианты, как нужно изменить задачу, чтобы не сталкиваться с достаточно сложными вопросами, которые в дальнейшем могут стать камнем преткновения для дальнейшего развития проекта.
Самое непонятное в статье — почему после бурного обсуждения так и не нашлось места и времени в самой статье дописать условие в запросах на ЭтоГруппа?
(116) а еще в п.3 есть СтрЗаменить для Пролог, но нет для Эпилог — а там тоже имя «Номенклатура» прописано.
Прошло давно много времени…
Иерархия без «В ИЕРАРХИИ» .
Размышляя об эффективных представлениях иерархии, понял, что задача об уровнях, глубине, прародителях, может быть решена в несколько раз быстрее, если отойти от построения полного замыкания и учесть особенности отношения иерархии.
Для этого требуется совсем чуть-чуть изменить предлагаемые запросы.
Новый варианты — в статье