Уровни, глубина, прародители, циклы и аналоги запросом
Файлы
ВНИМАНИЕ: Файлы из Базы знаний - это исходный код разработки. Это примеры решения задач, шаблоны, заготовки, "строительные материалы" для учетной системы. Файлы ориентированы на специалистов 1С, которые могут разобраться в коде и оптимизировать программу для запуска в базе данных. Гарантии работоспособности нет. Возврата нет. Технической поддержки нет.
Подписка PRO — скачивайте любые файлы со скидкой до 85% из Базы знаний
Оформите подписку на компанию для решения рабочих задач
Оформить подписку и скачать решение со скидкойВы можете заказать платную доработку или адаптацию этой разработки под вашу конфигурацию на «Бирже заказов».
- 0% комиссии — оплата напрямую исполнителю;
- Исполнители любого масштаба — от отдельных специалистов до команд под проект;
- Прямой обмен контактами между заказчиком и исполнителем;
- Безопасная сделка — при необходимости;
- Рейтинги, кейсы и прозрачная система откликов.
В предыдущей статье был рассмотрен прием «матричного умножения» в расчете транзитивного замыкания отношений, его теоретическое обоснование и реализация на платформе «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 записей регистра сведений "Аналоги номенклатуры" для этих связей.

После транзитивного замыкания аналогии будет сформирована полная таблица аналогов

Из-за фундаментального характера затрагиваемых понятий приведенные примеры, скорее всего, не исчерпывают всего списка применений предложенного приема. Надеюсь, приведенные решения являются достаточно поучительными и послужат хорошей основой для решения и других подобных практических задач.
Еще двум интереснейшим задачам-примерам будет посвящена отдельная статья.
Вступайте в нашу телеграмм-группу Инфостарт
См. также
Инструментарий разработчика Роли и права Запросы СКД Разработчик Руководитель проекта 1С:Предприятие 8 Платные (руб)
Инструменты для разработчиков 1С 8.3 и 8.5: Infostart Toolkit. Автоматизация и ускорение разработки на управляемых формах. Легкость работы с 1С.
16500 руб.
02.09.2020 286145 1608 423
Инструментарий разработчика Запросы Разработчик 1С 8.3 1С:Бухгалтерия 3.0 1С:Зарплата и Управление Персоналом 3.x Абонемент ($m)
Структура запроса. Обработка визуально показывает, из какого количества подзапросов состоит запрос.
2 стартмани
06.07.2026 5004 65 XSlava 20
Запросы Разработчик 1С 8.3 Россия Бесплатно (free)
Визуальный «Конструктор запроса» 1С, встроенный прямо в VS Code, — аналог конструктора из Конфигуратора и EDT, но без запуска платформы. Расширение читает конфигурацию, выгруженную в файлы, позволяет собрать запрос мышью и генерирует корректный текст на языке запросов 1С (SDBL). Открывает уже существующий текст запроса из кода для правки и — что особенно ценно — сохраняет комментарии при цикле «открыть - править - сохранить». Подходит тем, кто пишет код 1С в VS Code, держит под рукой множество проектов и не хочет ради одного запроса запускать тяжёлый Конфигуратор или EDT.
26.06.2026 10725 Alex_YAM 7
Нейросети Инструментарий разработчика Запросы Разработчик 1С:Предприятие 8 Абонемент ($m)
Консоль запросов 1.0: добавлен ИИ-помощник (запрос в DeepSeek), который помогает быстрее получать каркас Запроса 1С Сформулируйте простое описание; нажмите кнопку – получите результат прямо в консоли. Где дальше его можно дорабатывать и тестировать. 28.5.2026 Добавил "Консоль запросов 2.0" (запрос в DeepSeek + Claude) 4.10.2026 Обновил "Консоль запросов 2.0"
3 стартмани
20.05.2026 9620 46 German4739 72
WEB-интеграция Запросы Разработчик 1С 8.3 Абонемент ($m)
Post1C - это внешняя обработка, которая превращает 1С в полноценный инструмент для тестирования REST API. Всё управление сосредоточено в одном окне: настройка запроса, выполнение, просмотр ответа и генерация кода - без переключения между формами. Аналог Postman, но работающий в привычной среде 1С.
1 стартмани
02.04.2026 4709 106 priem_nv 28
Запросы Механизмы типовых конфигураций Разработчик 1С:Предприятие 8 1С:Управление торговлей 11 Россия Бесплатно (free)
Предлагается обзор работы с объектной моделью запроса по схемам запросов типовым функционалом УТ 11.5.
09.10.2025 13448 aximo 18
Инструментарий разработчика Запросы Разработчик 1С:Предприятие 8 1С:Зарплата и кадры государственного учреждения 3 1С:Зарплата и Управление Персоналом 3.x Абонемент ($m)
QueryConsole1C — расширение, включающее консоль запросов с поддержкой исполняемых представлений — аналогов виртуальных таблиц, основанных на методах программного интерфейса ЗУП. Оно позволяет выполнять запросы с учётом встроенной бизнес-логики, отлаживать алгоритмы получения данных и автоматически генерировать код на встроенном языке 1С.
1 стартмани
16.05.2025 13726 168 zup_dev 32
Инструментарий разработчика Запросы Разработчик 1С:Предприятие 8 1С:ERP Управление предприятием 2 Абонемент ($m)
В данной публикации рассказывается о решении, которое позволяет находить сразу все ошибки в тексте запроса за раз, а не только самую первую.
2 стартмани
05.03.2025 7767 25 XilDen 12
Но вот я сижу, и никак не могу представить разузлование спецификации номенклатуры этим методом. Слишком много требуемых параметров при расчете конечного расхода, не буду все перечислять, т.к. это можно увидеть в соответствующей функции в УПП например. Да, если бы нам просто требовались связи, возможно. Но нам требуются не связи, а конечный факт расчета, списания и получения.
По поводу аналогов, они требуются для работы обычного персонала, а не математиков аналитиков. Да и интерфейс последовательной связи аналогов будет весьма замудреным. Покажите мне Вашим способом запрос, который получит конечный расход аналогов по спецификации, с учетом приоритетов, остатков аналогов в НЗП по подразделениям, всех назначенных особенностей использования (с учетом пустых значений, которые "для всех"), расчета с учетом коэффициента и единиц измерения. Не забудьте, что остатки должны списываться последовательно до нуля с учетом приоритетов. У меня например получился один пакетный запрос из 12ти запросов в 800 строк, и это, естественно, с учетом работы типовой функции разузлования спецификаций, и это только для РА, а потом пришлось создавать аналог для ПУ для 4х запросов и связывать вставки кусков с комментариями в первом запросе. Если представить весь алгоритм полностью, натянутым на ваш механизм, то пошло оно все нафиг :) Я и так от этого контура ползаю крабом по столу. Рекурсия куда проще и понятней, я пожалел, что вообще взялся тогда решать эту задачу одним запросом. Не даром этот модуль 1С до сих пор оставляет в полуавтоматическом режиме (распределение аналогов при распределении материалов в спецификации).
Это я тут наумничал к тому, что пока не будет нормальных хранимых процедур делать подобные вещи совершенно дико. Собрать текст запроса по кусочкам в случае работы с иерархией выглядит понятней, проще, а значит удобней для редактирования. Поясню, это лишь моё мнение.
По-поводу аналогов. Пользователя не нужно пугать терминами. Ему нужно дать возможность вводить информацию в любом удобном и компактном виде, а перед использованием (или проверкой) разворачивать все аналоги, в том числе выводимые из основных. Второе, более тонкое, соображение, касается автосписания по спецификациям с учетом аналогов, приоритета, наличия и прочее. Неоднократно приходилось спорить с пользователями, которые ОШИБОЧНО считают автосписание частью автоматизации, потому что сам СЧИТАЮ АВТОСПИСАНИЕ ЗЛОМ. Вместо учета нас заставляют моделировать действительность. Обратная связь - основа управления - разрывается! Мы перестаем знать, что НА САМОМ ДЕЛЕ пошло на выпуск. Однако эти рассуждения уже не по теме данной статьи.
Кстати, я знаю (сейчас вот подумал), как решить задачу зацикливания значений реквизитов или зацикливания значений реквизитов табличной части одним пакетным запросом, причем всего с одной или двумя временными таблицами (в зависимости от способа). Т.е. будет работать на много быстрей вашего способа или способа вложенности, так как крутит всего одну индексированную или две таблицы, а не кучу обращений к базе данных. Более того, это способ, результат которого можно снабдить связанными данными, т.е. он будет иметь практическое применение. Его можно разворачивать последовательно или параллельно, т.е. в одну или в две стороны. Можно получать замкнутые или разомкнутые системы и весь путь от начала до конца, а не только начало и конец.
Да запрос будет выглядеть дико, иметь только конечное число разворачиваний. При динамическом задании количества разворачиваний можно сделать динамическое построение запроса. Вот про это может напишу статью, хотя опять же нормальный человек просто будет использовать рекурсию при контроле например при записи или в сервисном механизме, подумаю надо ли мне позорится.
Если действительно интрересно, то на мое решение зацикливания меня навел способ, который используется в C# при обработке LINQ запроса обобщенных типов. И при получении его результата в виде дерева. Ну или как обрабатывается динамические параметры обобщенных типов при формировании кортежа в том же C#. Учите мат часть, а не скрипите зубами (как уже тут кто-то писал).
Больше качественного и полезного материала!
По поводу автосписания, действительно долгий и не нужный спор. Я автоматизирую крупные предприятия, где "расставлять флажки" списания аналогов не реально в принципе. Тысячи наименований аналогов и их текущие разбитые остатки в НЗП. Поэтому делается одним этапом в конце периода подводя под инвентаризацию. Здесь без автосписания аналогов по исходным комплектующим из спецификаций никак в принципе. Ваши же размышления более походят на теоретические, хотя, безусловно, доля смысла в них есть.
1) определение всех предков всех потомков всех элементов(транзитивное замыкание) - написать первоначальный запрос и статью меня побудили два вопроса в форуме - значит, этот вопрос интересен, а решения никто кроме меня не предложил;
2) определение уровней всех элементов справочника - есть статья, где тот же вопрос решается гораздо более громоздким способом;
3) максимальная глубина справочника - долгий цикл или громоздкий метод на Наше1С (автор, видимо, задачу не из пальца высосал);
4) прародители - на мисте штук шесть веток про решение через ИТОГИ В ИЕРАРХИИ - там, что, одни теоретики собрались?
5) циклы в спецификациях - две статьи на Инфостарте. В одной - запрос в цикле, в другой процедура до 10 уровней максимум. Откуда взята проблема? Не из практики?
6) аналоги - посмотрите внимательно рисунок в статье: 5 записей в регистре и 14 в результате. Это реальная задача.
1,2,3,4,5 Изначально требовалось написать один пакетный запрос, без дополнительных построений. Так что Вы задачу не решили. Конструировать запрос средствами языка можно для любой из этих задач, причем более эффективно, чем у Вас. Для каждой конкретной задачи имеется ввиду.
6. Изначально задача туповатая. Имея лишь справочник номенклатура, невозможно пользоваться всеми преимуществами аналогового учета, как например в УПП. Просто человек не в теме.
Некоторые на нашей памяти прикручивали ООП к 1С помница. Да, достоинств много, но нафиг никому не нужно.
Решение у Вас красивое, умно выглядит. Но на практике для меня например совершенно не применимо.
Больше не буду спорить, сорри :)
1)Решение важных народохозяйственных задач :)
2)интеллектуальное удовольствие.
Извини, но не понял пункт 4 с песенкой. Можно ткнуть пальцем, где там цикл ? я цикла не вижу. Куплет не состоит из песенок.
Сергей (ildarovich).
Очень хорошая и интересная статья. Как и все другие Ваши статьи по теме "Делаем запросом". Однако ни одна из них не может иметь практического применения т.к. не учитываются "особенности" многопользовательского и "интерактивного" доступа к информации.
Ну, например, какой смысл в "заранее определить «высоту» (число этажей) отображения списка"(с), если к моменту его реального отображения "высота" может измениться?
Или какой смысл в "определение циклов" на определенный момент состояния БД, если с общей информацией работают (исправляют/плодят циклы) несколько пользователей?
Для решения подобных задач и предложены в 1С способы (инструмент) работы с т.н. "объектной моделью" с привлечением инструментов блокировки отдельных записей/объектов. Увы, медленно работающие т.к. реализуются, опять таки, запросами. И с очень неэффективным выполнением элементарных машинных команд обработки информации в оперативной памяти.
Я понимаю, что в среде 1С-а приходится подбирать алгоритмы под схему базы данных, а не наоборот (как этого требует суть самой идеи БнД) - разрабатывать схему базы данных под постановку изначальной задачи. Но, думаю, даже в этих "узких" рамках можно и нужно разрабатывать алгоритмы имеющие возможность практического применение.
P.S. Поставил "плюс" под публикацию и (1) сообщение.
Кстати, о том, что эта ветвь математики недалека от практики и нужна бухгалтерам свидетельствует общий модуль "КорректировкаСтоимости" в БП2, с которым как раз сейчас возимся (нестабильная работа штатного запроса). Там идет речь о разрыве циклов графа перемещений номенклатуры и построении остовного дерева.
Теперь о структуре базы данных «под конкретную задачу». Вопрос был задан про реализацию задачи на 1С и, поэтому, задача была решена на 1С. Если бы Вас спросили «на каком автобусе можно доехать до библиотеки», а Вы бы ответили, что лучше лететь на дирижабле, то Вас бы просто не поняли (может быть, кроме тех, кто стоит в московской пробке). Первоначально метод работал на совсем другом языке.
Вообще я не смогу Вас переубедить, когда Вы считаете, что архитектура платформы 1С никуда не годится. Могу лишь высказать свое мнение. Разрабатывать схему БД с нуля под конкретную задачу дорого. Если сравнить со строительством: 1С – это панельные многоэтажки – дешевое жилье – средство массовой автоматизации – в тесноте, да не в обиде. Единичные организации могут нанять Нормана Фостера для точной реализации своих требований. Остальным приходится использовать дешевые массовые проекты – реляционные СУБД с жесткой объектной моделью. Кроме того, знаком с печальным опытом использования Caсhe в нескольких проектах. Глазами разработчика все выглядит совсем не так радужно, как в теории. Объектная модель 1С выдерживает проверку временем – «застроены и заселены огромные территории». Кроме того, она развивается. Недавно появилась технология внешних источников данных. И если в какой-то задаче требуется более высокая реактивность (например, биллинговые системы), теперь можно бесшовно состыковать 1С с другой СУБД. У нас, например, так сделан обмен с импортной ERP.
Но все же не хотел бы уводить разговор от основной темы – работы предлагаемых приемов в детально описанных ПРАКТИЧЕСКИХ задачах.
"Получается, что решен ПРАКТИЧЕСКИЙ вопрос."(с)
Сергей (ildarovich).
Нет. Предложен способ выхода из угла, в который сами себя загнали. :-(
В рамках 1С-возможностей совершенно реально и логичнее обеспечить, например, проверку "циклов" в процессе ввода информации. Равно как и обеспечить «высоту» для последующего её получения ОДНИМ обращение к БД. И для решения этих задач совершенно не требуется каждый раз перебирать "всю" информацию.
Я совершенно "не против" красивых/эффективных алгоритмов обработки информации запросами. Но всему свое место. Запрос для пакетной обработки информации (отчетов). Прямой доступ для "интерактивных" многопользовательских систем. Разные задачи (цели). Разные способы (алгоритмы) решения. Разные инструменты. Разные схемы БД (даже в рамках реляционной модели!!!).
Т.е. я об этом написал в (35) сообщении...
P.S. Соглашусь с Вашим отнесением подобных решений к практическим, если Вы дадите описание алгоритма поиска "циклов" и ИСПРАВЛЕНИЯ "циклов" запросами в многопользовательской среде. И так, чтобы эта задача не превращалась в подобие перфокарточной/пакетной технологии середины прошлого века.
Растут стихи, не ведая стыда
и причем тут ваш метод, где допущена обычная для 1с-ника ошибка?
Почему обычная? Потому что 1С "настраивает" всех своих программистов не на ПРЕДУПРЕЖДЕНИЕ и ПОИСК ошибок на этапе разработки, а исправление по факту по принципу: "Если ничего не случилось - ну и ладно".
А по-простому - "авось, пронесет".
да, в 1С сделано все, чтобы было как можно запутаннее, не говоря уже об отсутствии инструментов моделирования работы обработок\обменов, контроля исполнения кода и редактирования понаделанного и понаписанного.
А мой метод (прием) при том, что он позволяет , например, в этом конкретном случае обнаружить ошибки, появившиеся в результате программного сбоя (у этого термина есть точное определение), сэкономить время и нервы большого количества людей.
так есть несколько способов сделать это, окромя вашего метода.
и их не исправили, как это делают в 1С?
А вот в реальных базах в конце концов все тухнет и падает, хоть в 8.1, хоть в 8.2, и 8.3 - что, выделяется из этого ряда?
я вот ... не могу уж очень сильно уважать РАЗРАБОТЧИКОВ ЯП, в котором сделано многое для запутывания разработки и усложнения интерфейса между этим самым ЯП и разработками, которые он пытается усиленно пользовать.
т.е. ваш поиск Родителей - это уже контроль зацикливания, правильно?
Ну открывайте тему тогда новую :)
В последних двух Ваших темах не просматривается какой-либо механизм контроля зацикливания чего-либо.
Разверните мысль тогда.
Потому как представленный в этой статье пример "контроля зацикливания" реализован все на той же проверке иерархии родителей и сравнении между родителей элементов между собой (выявляются цепочки Родитель1 - Потомок1, он же Родитель 2 - Потомок2, который Родитель3 - Потомок3 (который одновременно является Родитель1) - видите, вся суть статьи в одном предложении :)), коя реализутся - правильно, - не только вашим методом :)
Если вы реализовали некий механизм контроля, используя свой метод поиска родителей - так и описывайте именно его, а не рассказывайте о своей статье "О родителях" как о статье "Контроль зацикливания".
Сначала хотя б напишите её, чтоб было, что обсуждать :)
Ее БЫСТРЫЙ способ получения позволяет решить несколько совершенно РАЗЛИЧНЫХ практических задач (об этом вторая статья).
Например, определить всех опосредствованных родителей любого элемента справочника. А раз всех, значит, первоначальных, я их назвал прародителями (родоначальники). Это можно сделать и другим способом, как Вы правильно заметили, но НЕ В ПАКЕТНОМ ЗАПРОСЕ и НЕ ТАК БЫСТРО как "моим" способом.
На базе той же таблицы можно НАЙТИ ЦИКЛЫ - это совершенно другая задача. Из другой оперы, так сказать. Для этого сначала считается уровень каждого элемента. Под уровнем понимается число записей, в которых элемент справа. Далее находятся записи в исходной таблице, уровень элементов в которых одинаков. Не очевидно, но такие записи образуют цикл! А обработка их супербыстро находит!
Поэтому очень Вас еще раз прошу ответить, какие способы Вы имели в виду, говоря
Где Родитель1 = Потомок1000Я понимаю, вы бы еще привели пример с зацикливанием реквизитов, более реалистичный. Но опять же, легко решается кстати без выстраивания всей цепочки.
Ваши разработки могут служить лишь для собирания статистики в запущенных случаях. Кому она нужна, вопрос.
P.S. Я вспомнил один подобный случай, еще на 8.0 давно очень. Обмен явно издевался и выстраивал перепендикулярный гемор, как раз по связи владелец-реквизит. В конфигурации был очень хитроперевернуто решен механизм комплектации, который по сути повторял 7.7. того-же творца. Так вот, паренек, который на тот момент работал с 1С меньше года решил эту проблему на SQL, и за что был анально покаран. После этого, он написал триггер, который пузырьковал эту штуку. Я его реально зауважал. Вот, стремитесь. Это всё не сложно, логично и выглядит действительно круто.
Пример с зацикливанием реквизитов я привел в основной статье в пункте 4. Спасибо, что сочли его реалистичным.
По прежнему жду от AlexO подтверждения того, что
Например движение из двух точек к центру быстрей, чем движение от А до Б метода цепочки.
И кстати, всегда метод пересечения подмножеств, будет быстрей цепочки, если нужно собрать всю статистику разом, даже при количестве записей более миллиона.
И еще я бы рассмотрел анализ прямой вложенности запроса, при условии, что она работает на 10% быстрее, в случае длинных связей большой прирост производительности. Другой вопрос, где оно обломается, я думаю вы уже обсуждали этот вопрос.
Но это философия. Если правильно архитектурно подходить, то необходимо создавать систему контроля при вводе данных и где не критична скорость, потому что проконтролировать использование таких данных, вы можете только тогда, когда сами продолжаете разработку. А это в наше время непозволительная роскошь. А вас потом вспомнят "добрым словом".
Сергей (ildarovich).
Очень огорчил меня этот Ваш текст.
Попробую привести пример:
Имеем в базе данных иерархическую структура. Не имеет значение её прикладное назначение и "интенсивность внесения изменений"(с). По определению "задачи" в ней могут быть "циклы". Их надо искать и исправлять. Запускаем Ваш алгоритм. Получаем информацию о нескольких "циклах". Хотим исправить ошибки побыстрей. Сажаем двух операторов. И они начинают исправлять ошибки. Допустим, что самым простейшим способом - удалением "плохих" узлов. Смотрят, грубо говоря, каждый в свою "распечатку". Какова вероятность того, что один из операторов начнет исправлять уже исправленный "цикл" другим оператором? Т.е. им надо обеспечить постоянное обновление "распечатки" с учетом изменений после каждой процедуры исправления? Каким алгоритмом это следует делать? Как учесть в Вашем алгоритме тот факт, что в момент его выполнения под нужды одного оператора, другой оператор уже "прицелился" к очередному "циклу" и "нажимает" кнопку удаления?
"просто не делайте этого! (организационное решение)"(с)
Сергей (ildarovich).
Эта шутка мне больше понравилась, чем про три клавиши. :-)
Еще раз на пальцах.
Запрос - это пакетная обработка информации.
Пакетная обработка имеет ряд "характеристик" (упрощенно говоря!!!):
1) Скорость выполнения.
2) Количество информации "вовлеченной" в получение результата.
3) Время актуальности конечного результата.
Вы сосредоточились ТОЛЬКО на первом пункте.
Но по сути ВСЕХ Ваших решений требуется выполнять "интерактивную" обработку информации на основании результатов "пакетной" обработки. Чем больше информации "вовлечено" в получение результата пакетом, тем выше вероятность потери её актуальности. И меньше времени она является актуальной.
Для решения этой "проблемы" применяются блокировки. В Ваших алгоритмах требуется блокировка ВСЕЙ таблицы на ВСЁ время выполнения "пакета" и последующего воздействия на информацию в интерактивном режиме. Т.е. - однопользовательский режим обработки информации. И уменьшение времени выполнения "пакета" - это только "Игры разума"(с).
Сергей. Это элементарные, очевидные вещи. Если не смотреть на задачи "узко-конкретно"(с).
Я очень огорчен. Желаю успехов...
Далее, при чем здесь платформа 1С? Прием с тем же успехом можно реализовать в виде RCTE на SQL. Возможно, если бы sql.ru был бы таким же «нарядным» как Инфостарт, я бы предлагал прием там.
Так что не огорчайтесь! Смотрите: весна, все цветет, солнце, птички поют, кенгуру прыгают! – Радуйтесь! Вы скажете – при чем здесь Австралия! Вы притянули за уши надуманную задачу и расстроились. Я присоединил за уши Австралию и обрадовался! Чувствуете разницу? Так что больше позитива – больше креатива! Успехов и вам!
Нормальные же люди взяли на заметку, и при случае применят без криков.
Что собственно хотел сказать.
Возникла задача определить максимальный уровень группы справочника "Номенклатура", не важно есть в ней элементы, или нет.
В итоге вышло (75).
У нас в справочнике "Номенклатура" - ~200К элементов, ~3,5К групп.
На этих данных функция "ГлубинаИерархии" "работала" 2,5 минуты (база тестовая, файловая).
Предлагаемый мной вариант - меньше 5 секунд.
Т.е разница на порядок как минимум. Тестировать более строго пока нет возможности.
Но все же, чтобы соревнование было честным, Вам нужно поставить условие
Номенклатура.ЭтоГруппа = Истина
и в мой запрос тоже. В пролог.
Пролог = "ВЫБРАТЬ Родитель НачалоДуги, Ссылка КонецДуги ПОМЕСТИТЬ ЗамыканияДлины1 ИЗ Справочник.Номенклатура
| ГДЕ Родитель <> Значение(Справочник.Номенклатура.ПустаяСсылка) И Ссылка.ЭтоГруппа
| ОБЪЕДИНИТЬ ВЫБРАТЬ Ссылка, Ссылка ИЗ Справочник.Номенклатура;";
Сообщите, пожалуйста, результат.
И еще, раз Вы заинтересовались, хотел еще раз подчеркнуть факт, что приведенные функции приведены как функции для примера. Я считал, что построенные запросы будут в виде текстов использоваться внутри других запросов. Чтобы их результаты могли обрабатываться дальше. Я как-то это недостаточно ясно выразил в статье, судя по стандартной реакции читателей.
Я не собирался устраивать соревнования, только хотел поделиться придуманным мной велосипедом. На целую статью наверное маловато текста, а как комментарий/дополнение к вашей публикации - вполне.
Попробовал ваш вариант с ограничением по группам - значительно лучше, 20с
Пролог = "ВЫБРАТЬ Родитель НачалоДуги, Ссылка КонецДуги ПОМЕСТИТЬ ЗамыканияДлины1 ИЗ Справочник.Номенклатура
| ГДЕ Родитель <> Значение(Справочник.Номенклатура.ПустаяСсылка) И Ссылка.ЭтоГруппа
| ОБЪЕДИНИТЬ ВЫБРАТЬ Ссылка, Ссылка ИЗ Справочник.Номенклатура И Ссылка.ЭтоГруппа;";
Также взглянул на Ваш код. Возможно, Вам понравится использовать конструкцию, которую использую в этом случае я:
Для каждого СтрокаДерева Из МассивСтрок Цикл
МаксимальныйУровень = МАКС(МаксимальныйУровень, СтрокаДерева.Уровень())
КонецЦикла;
Запрос.Текст = "ВЫБРАТЬ
| 1 КАК Ссылка
|ИЗ
| Справочник.Номенклатура КАК Номенклатура
|ГДЕ
| Номенклатура.ЭтоГруппа = Истина
|ИТОГИ ПО
| Номенклатура.Родитель.Ссылка ИЕРАРХИЯ";
РезультатЗапроса = Запрос.Выполнить();
Дерево = РезультатЗапроса.Выгрузить(ОбходРезультатаЗапроса.ПоГруппировкамСИерархией);
МаксимальныйУровень = 0;
МассивСтрок = Дерево.Строки.НайтиСтроки(Новый Структура("Ссылка", NULL), Истина);
Для каждого СтрокаДерева Из МассивСтрок Цикл
Уровень = СтрокаДерева.Уровень();
Если МаксимальныйУровень < Уровень Тогда
МаксимальныйУровень = Уровень;
КонецЕсли;
КонецЦикла;
МаксимальныйУровень = МаксимальныйУровень + 1;
ЗЫ Не заметил, что ответ приведен двумя постами выше. Но задать МаксимальнаяДлинаПути = 1000 неразумно, т.к. столь большое значение снизит производительность функции. Получается, надо "на глазок" задавать достаточно-реалистичное значение этого параметра (слегка с запасом).
Тут, в примере:
Эпилог = "ВЫБРАТЬ КОЛИЧЕСТВО(НачалоДуги) - 1 Предок, КонецДуги Потомок ИЗ ЗамыканияДлины#2 СГРУППИРОВАТЬ ПО КонецДуги";можно определить последний уровень, а вот можно ли как-то получить номера уровней всех групп?
Например:
Предок Потомок Уровень
Группа1 | Элемент | 1
Группа2 | Элемент | 2
Группа3 | Элемент | 3
Группа4 | Элемент | 4
Ну а если затруднения в том, чтобы отобрать группы, то посмотрите начало запроса в комментарии (79). Там в два места добавлена проверка Ссылка.ЭтоГруппа. Таким тогда и должен быть пролог запроса.
приведенный код позволяет определить уровень группы в который входит элемент. Для моей задачи нужно знать уровень группы в который входит элемент, а также уровень группы в которую входит группа в которую входит элемент :) и т. д.
Эпилог = "ВЫБРАТЬ УровниПредков.Уровень, УровниПредков.Предок
|Из (ВЫБРАТЬ КОЛИЧЕСТВО(НачалоДуги) - 1 Уровень, КонецДуги Предок
|ИЗ ЗамыканияДлины#2
|СГРУППИРОВАТЬ ПО КонецДуги) КАК УровниПредков
|СОЕДИНЕНИЕ ЗамыканияДлины#2 КАК Связи
|ПО УровниПредков.Предок = Связи.НачалоДуги
|И Связи.КонецДуги = &ЗаданныйЭлемент";Только вот требуется помощь. Долгое время уже кручу и так и сяк, не могу найти нормального решения. Может подмогнете?
Исходные данные:
Иерархический справочник с подчинением элементам. Глубина неизвестна (предположим до 10 уровней). Длина веток может быть любой.
Задача
Получить Список элементов справочника с заданным родителем. В выборку должны попасть только те элементы у которых хотя бы один потомок, находящийся на самом нижнем уровне удовлетворяет условию типа Элемент.Свойство=&МоеСвойство
1) по структуре справочника получаете таблицу пар предок-потомок. Это делается так же, как в любом из примеров 1-3.
2) теперь ставите условие:
-) предок должен быть равным заданному. Таким образом отбираете все узлы, транзитивно входящие в заданный.
-) потомок должен иметь заданное значение свойства.
То есть меняется только Эпилог запроса.
В результате запрос будет иметь вид:
ВЫБРАТЬ
Номенклатура.Родитель КАК НачалоДуги,
Номенклатура.Ссылка КАК КонецДуги
ПОМЕСТИТЬ ЗамыканияДлины1
ИЗ
Справочник.Номенклатура КАК Номенклатура
ГДЕ
Номенклатура.Родитель <> ЗНАЧЕНИЕ(Справочник.Номенклатура.ПустаяСсылка)
ОБЪЕДИНИТЬ
ВЫБРАТЬ
Номенклатура.Ссылка,
Номенклатура.Ссылка
ИЗ
Справочник.Номенклатура КАК Номенклатура
;
//////////////////////////////////////////////////////////// ////////////////////
ВЫБРАТЬ РАЗЛИЧНЫЕ
ПерваяДуга.НачалоДуги,
ВтораяДуга.КонецДуги
ПОМЕСТИТЬ ЗамыканияДлины2
ИЗ
ЗамыканияДлины1 КАК ПерваяДуга
ВНУТРЕННЕЕ СОЕДИНЕНИЕ ЗамыканияДлины1 КАК ВтораяДуга
ПО ПерваяДуга.КонецДуги = ВтораяДуга.НачалоДуги
;
//////////////////////////////////////////////////////////// ////////////////////
ВЫБРАТЬ РАЗЛИЧНЫЕ
ПерваяДуга.НачалоДуги,
ВтораяДуга.КонецДуги
ПОМЕСТИТЬ ЗамыканияДлины4
ИЗ
ЗамыканияДлины2 КАК ПерваяДуга
ВНУТРЕННЕЕ СОЕДИНЕНИЕ ЗамыканияДлины2 КАК ВтораяДуга
ПО ПерваяДуга.КонецДуги = ВтораяДуга.НачалоДуги
;
//////////////////////////////////////////////////////////// ////////////////////
ВЫБРАТЬ РАЗЛИЧНЫЕ
ПерваяДуга.НачалоДуги,
ВтораяДуга.КонецДуги
ПОМЕСТИТЬ ЗамыканияДлины8
ИЗ
ЗамыканияДлины4 КАК ПерваяДуга
ВНУТРЕННЕЕ СОЕДИНЕНИЕ ЗамыканияДлины4 КАК ВтораяДуга
ПО ПерваяДуга.КонецДуги = ВтораяДуга.НачалоДуги
;
//////////////////////////////////////////////////////////// ////////////////////
ВЫБРАТЬ РАЗЛИЧНЫЕ
ПерваяДуга.НачалоДуги,
ВтораяДуга.КонецДуги
ПОМЕСТИТЬ ЗамыканияДлины16
ИЗ
ЗамыканияДлины8 КАК ПерваяДуга
ВНУТРЕННЕЕ СОЕДИНЕНИЕ ЗамыканияДлины8 КАК ВтораяДуга
ПО ПерваяДуга.КонецДуги = ВтораяДуга.НачалоДуги
;
//////////////////////////////////////////////////////////// ////////////////////
ВЫБРАТЬ
ЗамыканияДлины16.КонецДуги КАК Элемент
ПОМЕСТИТЬ Подчиненные
ИЗ
ЗамыканияДлины16 КАК ЗамыканияДлины16
ГДЕ
ЗамыканияДлины16.НачалоДуги = &ВыбНоменклатура
;
//////////////////////////////////////////////////////////// ////////////////////
ВЫБРАТЬ РАЗЛИЧНЫЕ
ЗамыканияДлины16.НачалоДуги КАК Элемент
ПОМЕСТИТЬ Содержательные
ИЗ
ЗамыканияДлины16 КАК ЗамыканияДлины16
ВНУТРЕННЕЕ СОЕДИНЕНИЕ РегистрСведений.ЗначенияСвойствОбъектов КАК ЗначенияСвойствОбъектов
ПО ЗамыканияДлины16.КонецДуги = ЗначенияСвойствОбъектов.Объект
И (ЗначенияСвойствОбъектов.Свойство = &ВыбСвойство)
;
//////////////////////////////////////////////////////////// ////////////////////
ВЫБРАТЬ
Подчиненные.Элемент
ИЗ
Подчиненные КАК Подчиненные
ВНУТРЕННЕЕ СОЕДИНЕНИЕ Содержательные КАК Содержательные
ПО Подчиненные.Элемент = Содержательные.Элемент ПоказатьПоследние три запроса легко объединяются в один с двумя соединениями.
У меня не верно выполняется след. условие
Вместо "на самом нижнем уровне" в результат запроса попадает "на любом из нижележащих уровней"
Т.е. из дерева
.....|_____ Услуги связи........(свойство ОК)
..........|___ Интернет.........(НЕ свойство ОК)
..........|___ Мобильная связь..(НЕ свойство ОК)
..........|___ Почта............(НЕ свойство ОК)
В выборку с родителем "Административные расходы" попадает "Услуги связи", хотя ни один элемент последнего уровня Услуг связи не удовлетворяет условию.
Текст моего запроса:
ВЫБРАТЬ
ФинансовыеОперации.Родитель КАК НачалоДуги,
ФинансовыеОперации.Ссылка КАК КонецДуги
ПОМЕСТИТЬ ЗамыканияДлины1
ИЗ
Справочник.ФинансовыеОперации КАК ФинансовыеОперации
//ГДЕ
// ФинансовыеОперации.Родитель <> ЗНАЧЕНИЕ(Справочник.ФинансовыеОперации.ПустаяСсылка)
ОБЪЕДИНИТЬ
ВЫБРАТЬ
ФинансовыеОперации.Ссылка,
ФинансовыеОперации.Ссылка
ИЗ
Справочник.ФинансовыеОперации КАК ФинансовыеОперации
;
//////////////////////////////////////////////////////////// ////////////////////
ВЫБРАТЬ РАЗЛИЧНЫЕ
ПерваяДуга.НачалоДуги,
ВтораяДуга.КонецДуги
ПОМЕСТИТЬ ЗамыканияДлины2
ИЗ
ЗамыканияДлины1 КАК ПерваяДуга
ВНУТРЕННЕЕ СОЕДИНЕНИЕ ЗамыканияДлины1 КАК ВтораяДуга
ПО ПерваяДуга.КонецДуги = ВтораяДуга.НачалоДуги
;
//////////////////////////////////////////////////////////// ////////////////////
ВЫБРАТЬ РАЗЛИЧНЫЕ
ПерваяДуга.НачалоДуги,
ВтораяДуга.КонецДуги
ПОМЕСТИТЬ ЗамыканияДлины4
ИЗ
ЗамыканияДлины2 КАК ПерваяДуга
ВНУТРЕННЕЕ СОЕДИНЕНИЕ ЗамыканияДлины2 КАК ВтораяДуга
ПО ПерваяДуга.КонецДуги = ВтораяДуга.НачалоДуги
;
//////////////////////////////////////////////////////////// ////////////////////
ВЫБРАТЬ
ЗамыканияДлины4.КонецДуги КАК Элемент
ПОМЕСТИТЬ Подчиненные
ИЗ
ЗамыканияДлины4 КАК ЗамыканияДлины4
ГДЕ
ЗамыканияДлины4.НачалоДуги = &ВыбРодитель
;
//////////////////////////////////////////////////////////// ////////////////////
ВЫБРАТЬ РАЗЛИЧНЫЕ
ЗамыканияДлины4.НачалоДуги КАК Элемент
ПОМЕСТИТЬ Содержательные
ИЗ
ЗамыканияДлины4 КАК ЗамыканияДлины4
ГДЕ
ЗамыканияДлины4.КонецДуги.МетодНачисления = &МетодНачисления
;
//////////////////////////////////////////////////////////// ////////////////////
ВЫБРАТЬ
Подчиненные.Элемент
ИЗ
Подчиненные КАК Подчиненные
ВНУТРЕННЕЕ СОЕДИНЕНИЕ Содержательные КАК Содержательные
ПО (Подчиненные.Элемент = Содержательные.Элемент
И Подчиненные.Элемент.Родитель = &ВыбРодитель) ПоказатьВ чем здесь моя ошибка? (не считая оптимизации)
ВЫБРАТЬ РАЗЛИЧНЫЕ
ЗамыканияДлины4.НачалоДуги КАК Элемент
ПОМЕСТИТЬ Содержательные
ИЗ
ЗамыканияДлины4 КАК ЗамыканияДлины4
ГДЕ
ЗамыканияДлины4.КонецДуги.МетодНачисления = &МетодНачисления
И ЗамыканияДлины4.КонецДуги В (ВЫБРАТЬ НачалоДуги
ИЗ ЗамыканияДлины1
СГРУППИРОВАТЬ ПО НачалоДуги
ИМЕЮЩИЕ (Количество(*) = 1))
; Показать- ошибка выполнения? - тогда скриншрт пришлите
- не находит циклы? - тогда желателен скриншот не одной спецификации, а всех, входящих в цикл.
Не заполняет вообще, скрин спецификаций из вашего запроса прикладываю или какой скрин вам нужен?
Цикл - это ситуация, когда
В большом наборе спецификаций обнаружить ошибку в виде цикла трудно. Решение из пункта 4 упрощает решение этой задачи. И если на вашем наборе данных обработка не находит циклов (ошибок), возможно, значит, что циклов (ошибок) в вашем наборе нет.
Тогда понятно, я просто думал, что это функция позволить мне вернуть таблицу значений разобранную и по ней уже работать, а так всё равно писать надо будет!
Очень интересно, действительно впечатляет.
А насчет максимальной глубины справочника, я, конечно, чувствую себя жутким быдлокодером, но всегда использовал что-то типа такого решения:
ВЫБРАТЬ
КОЛИЧЕСТВО(РАЗЛИЧНЫЕ Номенклатура.Ссылка) КАК Ссылка
ИЗ
Справочник.Номенклатура КАК Номенклатура
ГДЕ
Номенклатура.Родитель...n....Родитель = ЗНАЧЕНИЕ(Справочник.Номенклатура.ПустаяСсылка)
Минус метода, что надо знать примерно возможную верхнюю границу количества родителей. Если с ней получаем 0, то затем количество родителей уменьшаем n на один до тех пор, пока результат запроса не станет равен 0.
Если значение верхней границы очень уж высоко, то делать вручную неудобно и тогда этот запрос пихаем в обработку, уменьшая n (количество родителей) в запросе программно.
Насчет быстродействия не знаю, но для моих задач всегда быстро срабатывало.
ВЫБРАТЬ
КОЛИЧЕСТВО(РАЗЛИЧНЫЕ Номенклатура.Ссылка) КАК Ссылка
ИЗ
Справочник.Номенклатура КАК Номенклатура
ГДЕ
Номенклатура.Родитель...n....Родитель = ЗНАЧЕНИЕ(Справочник.Номенклатура.ПустаяСсылка)
извиняюсь за некропостинг, но такой метод можно ускорить с помощью половинного деления. Тогда в худшем случае потребуется не N раз выполнять запрос, а только log2n раз. (берем стартовое значение, например 10, если взяли мало, то умножаем на 2, если много, то делим пополам отрезок и т.д.)
В вашем предложении "если взяли мало, то умножаем на 2" означает, что запрос и все неявные соединения в нем выполняются много раз заново. Это очень расточительно. Поэтому метод, предложенный в статье, который решает ту же задачу с использованием в единственном пакетном запросе log2(n) соединений, вместо n соединений, будет работать быстрее.
Вообще выигрыш (при использовании подхода, изложенного в статье) будет чуть меньше n/log2(n), так как потребуется время на запись элементов временных таблиц.
На самом деле такой запрос очень быстрый, так как он транслируется в запрос SQL с соединениями по индексированному полю Ссылка. В базе с количеством элементов номенклатуры около 112 тысяч, запрос с 7-ю обращениями к родителю выполняется за 0,3 сек. Причем при количестве соединений больше максимальной глубины иерархии, время выполнения запроса почти не увеличивается. Можете сами попробовать.
SELECT CAST(COUNT(DISTINCT T1.Ссылка) AS NUMERIC(10))
FROM Справочник.Номенклатура T1
LEFT OUTER JOIN Справочник.Номенклатура T2 ON T1.Родитель = T2.Ссылка
LEFT OUTER JOIN Справочник.Номенклатура T3 ON T2.Родитель = T3.Ссылка
LEFT OUTER JOIN Справочник.Номенклатура T4 ON T3.Родитель = T4.Ссылка
LEFT OUTER JOIN Справочник.Номенклатура T5 ON T4.Родитель = T5.Ссылка
LEFT OUTER JOIN Справочник.Номенклатура T6 ON T5.Родитель = T6.Ссылка
WHERE (T6.Родитель = @P1)
ПоказатьВозможно, проблема в том, что первый откорректированный документ нужно искать среди вообще всех документов, то есть нет никакой возможности предварительно сузить область поиска за счет отбора по сотруднику, подразделению, периоду и тому подобное. Тогда документов будет больше нескольких тысяч и метод, на вход которого нужно подать связи всех документов в области поиска, будет тормозить.
То есть метод рассчитан все же на выявление длинных связей среди относительно небольшого количества объектов (до нескольких тысяч). И отличается тем, что в процессе работы устанавливает (заодно) все связи, даже те, которые впоследствии могут и не понадобиться.
Метод нашел ПРАКТИЧЕСКОЕ применение в реальной задаче - поиск настройки номенклатуры на ближайшем ее родителе.
Проблема оперирования со всей таблицей была решена усложнением пролога, сдесь он получает все группы таблицы и только нужные элементы.
"ВЫБРАТЬ
| ТаблицаБД.Родитель КАК Родитель,
| ТаблицаБД.Ссылка КАК Ссылка,
| ИСТИНА КАК СсылкаЭтоГруппа
|ПОМЕСТИТЬ ДугиДлины_1
|ИЗ
| &ИмяТаблицыБД КАК ТаблицаБД
|ГДЕ
| ТаблицаБД.ЭтоГруппа
| И НЕ ТаблицаБД.Родитель = ЗНАЧЕНИЕ(Справочник.Номенклатура.ПустаяСсылка)
|
|ОБЪЕДИНИТЬ
|
|ВЫБРАТЬ
| ТаблицаБД.Ссылка,
| ТаблицаБД.Ссылка,
| ИСТИНА
|ИЗ
| &ИмяТаблицыБД КАК ТаблицаБД
|ГДЕ
| ТаблицаБД.ЭтоГруппа
|
|ОБЪЕДИНИТЬ
|
|ВЫБРАТЬ РАЗЛИЧНЫЕ
| ИсходнаяТаблица.Ссылка.Родитель,
| ИсходнаяТаблица.Ссылка,
| ЛОЖЬ
|ИЗ
| &ИмяТаблицыОбрабатываемыхДанных КАК ИсходнаяТаблица
|ГДЕ
| НЕ ИсходнаяТаблица.Ссылка.ЭтоГруппа
| И НЕ ИсходнаяТаблица.Ссылка.Родитель = ЗНАЧЕНИЕ(Справочник.Номенклатура.ПустаяСсылка)
| И &ТекстЗапросаУсловие
|
|ОБЪЕДИНИТЬ
|
|ВЫБРАТЬ РАЗЛИЧНЫЕ
| ИсходнаяТаблица.Ссылка,
| ИсходнаяТаблица.Ссылка,
| ЛОЖЬ
|ИЗ
| &ИмяТаблицыОбрабатываемыхДанных КАК ИсходнаяТаблица
|ГДЕ
| НЕ ИсходнаяТаблица.Ссылка.ЭтоГруппа
| И &ТекстЗапросаУсловие";
ПоказатьВ эпилоге получаются уровни группировок родителей, затем все это соединяется с регистром в котором хранятся настройки и группируются по МАКСИМУМ(Уровень).
В базе с 146тыс позиций номенклатуры и обработкой 1000 элементов с максимальной длиной 30 отрабатывает за менее 300мс.
P.S.: Писал запрос понятным для себя языком, т.к. не имею математического образования и все эти термины мне непонятны, поэтому стиль запроса немного отличается от того что в статье. Смысл при этом не поменялся. Также добавил поле СсылкаЭтоГруппа, т.к. в эпилоге накладываю на него отборы, через точку работает медленней на 50-100 мс.
P.P.S.: Попробовал выполнить оптимизацию запроса добавлением индексов во временные таблицы - ничего не добился, да и старался немного. SQL Profiler говорит о том, что основное время тратится на запись временной таблицы в tempdb. При этом каждая следующая таблица обрабатывает примерно за одинаковое время, т.е. увеличение максимальной длины будет добавлять в моем случае примерно 50мс к каждой новой таблице.
По поводу записи временной таблицы - полностью согласен. Этот фактор обязательно нужно учитывать. Недавно закончил развитие . Там оказалось, что учет времени записи элементов временной таблицы позволяет выбрать оптимальный размер группировок.
Я оцениваю этот метод как "Сложный". Это значит, что далеко не любой программист может сходу разобраться в коде, активно опирающемся на данную методику, что сужает для заказчика выбор людей для сопровождения готового проекта.
Несмотря на "сложность", часто, применение этого метода бывает единственно возможный выходом. Например, когда у заказчика уже давно используются структуры данных, на которых работали рекурсивные алгоритмы, а теперь данные разрослись и нужно вернуть приемлемое время выполнения.
Поэтому я считаю, что статья очень хорошая, обсуждать такие приемы на уровне "мне это в практике никогда не требовалось и не потребуется" считаю бесполезным занятием. Умение применять такие алгоритмы позволяет на этапе проектирования решения задачи заказчика оценить практическую осуществимость, сложность задачи по времени реализации и прожорливость реализуемого алгоритма с точки зрения машинного времени и памяти.
Кроме того, для меня подобный навык оставил еще один плюс. Теперь при разговоре с заказчиком я легко выявляю места на которых спотыкаются обычные методы решения и легко помогают замыкания. Я могу заранее предупредить заказчика и последствиях принятия задачи в том виде, как он ее сформулировал и показать варианты, как нужно изменить задачу, чтобы не сталкиваться с достаточно сложными вопросами, которые в дальнейшем могут стать камнем преткновения для дальнейшего развития проекта.
Размышляя об эффективных представлениях иерархии, понял, что задача об уровнях, глубине, прародителях, может быть решена в несколько раз быстрее, если отойти от построения полного замыкания и учесть особенности отношения иерархии.
Для этого требуется совсем чуть-чуть изменить предлагаемые запросы.
Новый варианты - в статье .
СтрЗаменить(Эпилог, "#2"
на
СтрЗаменить(СтрЗаменить(Эпилог, "Номенклатура", ИмяСправочника), "#2"
2. Быстрое определение максимальной глубины иерархии одним пакетным запросом.
Воспользовался вашей функцией и получил время выполнения запроса 21 секунду для определения максимальной глубины иерархии.
Потом написал простейшую функцию
Функция ПолучитьКоличествоУровнейИерархии()
Выход = Ложь;
УровнейИерархии = 0;
Запрос = Новый Запрос;
ТекстЗапроса = "ВЫБРАТЬ первые 1
| Номенклатура.Ссылка
|ИЗ
| Справочник.Номенклатура КАК Номенклатура
|ГДЕ
| НЕ Номенклатура%Шаблон% = ЗНАЧЕНИЕ(Справочник.Номенклатура.ПустаяСсылка)";
СтрокаЗамены = "";
Пока Не Выход Цикл
СтрокаЗамены = СтрокаЗамены + ".Родитель";
Запрос.Текст = СтрЗаменить(ТекстЗапроса,"%Шаблон%",СтрокаЗамены);
Выборка = Запрос.Выполнить().Выбрать();
Если Выборка.Количество() = 0 Тогда
Выход = Истина;
Иначе
УровнейИерархии = УровнейИерархии + 1;
КонецЕсли;
КонецЦикла;
Возврат УровнейИерархии;
КонецФункции ПоказатьВремя выполнения - 5 секунд.
То есть быстрее вашего запроса в 4 раза (тогда как в статье было обещано чуть ли не моментальное выполнение)!
Хороший прием. Думаю, его еще можно подкрутить, чтобы еще чуть-чуть время сократить. Но это комбинированная, а не не чисто запросная техника. Так не получится определить уровень, не выходя из запроса.
Не знаю, как вы так прочитали, но "чуть ли не моментальное время" я не должен был обещать. Этот запрос иллюстрирует частное применение одного общего приема и с точки зрения конкретной задачи делает лишние действия, откуда и такое время.
Более быстрый вариант есть в статье . Он там называется "Запрос для определения уровней групп и элементов справочника". Естественно, нужно еще определение максимума в конце добавить.
Доработанный текст запроса из статьи |
|---|
| ВЫБРАТЬ
Номенклатура.Родитель КАК Предок, 1 КАК Уровень, Номенклатура.Ссылка КАК Потомок ПОМЕСТИТЬ МР1 ИЗ Справочник.Номенклатура КАК Номенклатура ОБЪЕДИНИТЬ ВЫБРАТЬ ЗНАЧЕНИЕ(Справочник.Номенклатура.ПустаяСсылка), 0, ЗНАЧЕНИЕ(Справочник.Номенклатура.ПустаяСсылка) ; //////////////////////////////////////////////////////////// ВЫБРАТЬ Связь1.Предок КАК Предок, Связь1.Уровень + Связь2.Уровень КАК Уровень, Связь2.Потомок КАК Потомок ПОМЕСТИТЬ МР2 ИЗ МР1 КАК Связь1 ВНУТРЕННЕЕ СОЕДИНЕНИЕ МР1 КАК Связь2 ПО Связь1.Потомок = Связь2.Предок ; //////////////////////////////////////////////////////////// ВЫБРАТЬ Связь1.Предок КАК Предок, Связь1.Уровень + Связь2.Уровень КАК Уровень, Связь2.Потомок КАК Потомок ПОМЕСТИТЬ МР4 ИЗ МР2 КАК Связь1 ВНУТРЕННЕЕ СОЕДИНЕНИЕ МР2 КАК Связь2 ПО Связь1.Потомок = Связь2.Предок ; //////////////////////////////////////////////////////////// ВЫБРАТЬ Связь1.Предок КАК Предок, Связь1.Уровень + Связь2.Уровень КАК Уровень, Связь2.Потомок КАК Потомок ПОМЕСТИТЬ МР8 ИЗ МР4 КАК Связь1 ВНУТРЕННЕЕ СОЕДИНЕНИЕ МР4 КАК Связь2 ПО Связь1.Потомок = Связь2.Предок ; //////////////////////////////////////////////////////////// ВЫБРАТЬ МАКСИМУМ(Связь1.Уровень + Связь2.Уровень) КАК Глубина ИЗ МР8 КАК Связь1 ВНУТРЕННЕЕ СОЕДИНЕНИЕ МР8 КАК Связь2 ПО Связь1.Потомок = Связь2.Предок ГДЕ Связь2.Потомок <> ЗНАЧЕНИЕ(Справочник.Номенклатура.ПустаяСсылка) |
Попробуйте этот запрос на своих данных. И сообщите, пожалуйста, результат, если нетрудно.
Единственно, я дополнил запрос условием ЭтоГруппа.
В этом случае запрос действительно работает практически моментально.
ВЫБРАТЬ
Номенклатура.Родитель КАК Предок,
1 КАК Уровень,
Номенклатура.Ссылка КАК Потомок
ПОМЕСТИТЬ МР1
ИЗ
Справочник.Номенклатура КАК Номенклатура
ГДЕ Номенклатура.ЭтоГруппа
Дело в том, что без условия ГДЕ Номенклатура.ЭтоГруппа во временную таблицу помещается весь огромный справочник Номенклатуры, чего делать совсем не хочется, тем более с учетом того, что количество групп на порядок меньше количества элементов.
ВЫБРАТЬ
Номенклатура.Родитель КАК Предок,
1 КАК Уровень,
Номенклатура.Ссылка КАК Потомок
ПОМЕСТИТЬ МР1
ИЗ
Справочник.Номенклатура КАК Номенклатура
ГДЕ
Номенклатура.ЭтоГруппа
ОБЪЕДИНИТЬ
ВЫБРАТЬ
ЗНАЧЕНИЕ(Справочник.Номенклатура.ПустаяСсылка),
0,
ЗНАЧЕНИЕ(Справочник.Номенклатура.ПустаяСсылка)
;
//////////////////////////////////////////////////////////// ////////////////////
ВЫБРАТЬ
Связь1.Предок КАК Предок,
Связь1.Уровень + Связь2.Уровень КАК Уровень,
Связь2.Потомок КАК Потомок
ПОМЕСТИТЬ МР2
ИЗ
МР1 КАК Связь1
ВНУТРЕННЕЕ СОЕДИНЕНИЕ МР1 КАК Связь2
ПО Связь1.Потомок = Связь2.Предок
;
//////////////////////////////////////////////////////////// ////////////////////
ВЫБРАТЬ
Связь1.Предок КАК Предок,
Связь1.Уровень + Связь2.Уровень КАК Уровень,
Связь2.Потомок КАК Потомок
ПОМЕСТИТЬ МР4
ИЗ
МР2 КАК Связь1
ВНУТРЕННЕЕ СОЕДИНЕНИЕ МР2 КАК Связь2
ПО Связь1.Потомок = Связь2.Предок
;
//////////////////////////////////////////////////////////// ////////////////////
ВЫБРАТЬ
Связь1.Предок КАК Предок,
Связь1.Уровень + Связь2.Уровень КАК Уровень,
Связь2.Потомок КАК Потомок
ПОМЕСТИТЬ МР8
ИЗ
МР4 КАК Связь1
ВНУТРЕННЕЕ СОЕДИНЕНИЕ МР4 КАК Связь2
ПО Связь1.Потомок = Связь2.Предок
;
//////////////////////////////////////////////////////////// ////////////////////
ВЫБРАТЬ
Связь1.Предок КАК Предок,
Связь1.Уровень + Связь2.Уровень КАК Уровень,
Связь2.Потомок КАК Потомок
ПОМЕСТИТЬ МР16
ИЗ
МР8 КАК Связь1
ВНУТРЕННЕЕ СОЕДИНЕНИЕ МР8 КАК Связь2
ПО Связь1.Потомок = Связь2.Предок
;
//////////////////////////////////////////////////////////// ////////////////////
ВЫБРАТЬ
МАКСИМУМ(МР16.Уровень) КАК Глубина
ПОМЕСТИТЬ вт_Глубина
ИЗ
МР16 КАК МР16
ГДЕ
МР16.Потомок <> ЗНАЧЕНИЕ(Справочник.Номенклатура.ПустаяСсылка)
;
//////////////////////////////////////////////////////////// ////////////////////
ВЫБРАТЬ
МР16.Потомок КАК Потомок
ПОМЕСТИТЬ вт_ГруппМаксУровень
ИЗ
МР16 КАК МР16
ВНУТРЕННЕЕ СОЕДИНЕНИЕ вт_Глубина КАК вт_Глубина
ПО МР16.Уровень = вт_Глубина.Глубина
;
//////////////////////////////////////////////////////////// ////////////////////
ВЫБРАТЬ ПЕРВЫЕ 1
ИСТИНА КАК ЕстьЭлементы
ПОМЕСТИТЬ вт_ЭлементыМаксУровень
ИЗ
вт_ГруппМаксУровень КАК вт_ГруппМаксУровень
ВНУТРЕННЕЕ СОЕДИНЕНИЕ Справочник.Номенклатура КАК Номенклатура
ПО вт_ГруппМаксУровень.Потомок = Номенклатура.Родитель
;
//////////////////////////////////////////////////////////// ////////////////////
ВЫБРАТЬ
ВЫБОР
КОГДА ЕСТЬNULL(вт_ЭлементыМаксУровень.ЕстьЭлементы, ЛОЖЬ)
ТОГДА вт_Глубина.Глубина
ИНАЧЕ вт_Глубина.Глубина - 1
КОНЕЦ КАК Глубина
ИЗ
вт_Глубина КАК вт_Глубина
ЛЕВОЕ СОЕДИНЕНИЕ вт_ЭлементыМаксУровень КАК вт_ЭлементыМаксУровень
ПО (ИСТИНА) ПоказатьВооружился вашим запросом для решения такой задачи:
Есть периодический РС "AX_СтатусыЗакупок" с измерениями "Номенклатура, Склад". Номенклатура - только элементы, а Склад может быть как группа, так и элемент.
При проведении заказа (в ТЧ Товары есть номенклатура и склад) нужно проверять, а нету ли ограничений в регистре, при этом склады могут пересекаться, т.е. для одной и той же номенклатуры может существовать запись в РС с разными складами, один из которых прародитель/потомок другого и иметь разные ограничения проведения заказа, при этом приоритет записи будет у того, кто ниже уровнем с искомому складу из ТЧ товаров. Тяжело сформулировать, но я старался.
Получилось вот что:
ВЫБРАТЬ
ВЫРАЗИТЬ(Товары.НомерСтроки КАК ЧИСЛО) КАК НомерСтроки,
ВЫРАЗИТЬ(Товары.Номенклатура КАК Справочник.Номенклатура) КАК Номенклатура,
Товары.Склад КАК Склад
ПОМЕСТИТЬ ВТ_ТЧ
ИЗ
&Товары КАК Товары
;
//////////////////////////////////////////////////////////// ////////////////////
ВЫБРАТЬ
Товары.НомерСтроки КАК НомерСтроки,
Товары.Номенклатура КАК Номенклатура,
Товары.Склад КАК Склад
ПОМЕСТИТЬ ВТ_Товары
ИЗ
ВТ_ТЧ КАК Товары
ГДЕ
Товары.Номенклатура.ТипНоменклатуры = ЗНАЧЕНИЕ(Перечисление.ТипыНоменклатуры.Товар)
;
//////////////////////////////////////////////////////////// ////////////////////
ВЫБРАТЬ
AX_СтатусыЗакупокСрезПоследних.Период КАК Период,
AX_СтатусыЗакупокСрезПоследних.Номенклатура КАК Номенклатура,
AX_СтатусыЗакупокСрезПоследних.Склад КАК Склад,
AX_СтатусыЗакупокСрезПоследних.Статус КАК Статус
ПОМЕСТИТЬ ВТ_СтатусыЗакупокПоНоменклатуре
ИЗ
РегистрСведений.AX_СтатусыЗакупок.СрезПоследних(
,
Номенклатура В
(ВЫБРАТЬ
ВТ_Товары.Номенклатура
ИЗ
ВТ_Товары)) КАК AX_СтатусыЗакупокСрезПоследних
;
//////////////////////////////////////////////////////////// ////////////////////
ВЫБРАТЬ
Склады.Родитель КАК НачалоДуги,
Склады.Ссылка КАК КонецДуги
ПОМЕСТИТЬ ЗамыканияДлины1
ИЗ
Справочник.Склады КАК Склады
ГДЕ
Склады.Родитель <> ЗНАЧЕНИЕ(Справочник.Склады.ПустаяСсылка)
ОБЪЕДИНИТЬ
ВЫБРАТЬ
Склады.Ссылка,
Склады.Ссылка
ИЗ
Справочник.Склады КАК Склады
;
//////////////////////////////////////////////////////////// ////////////////////
ВЫБРАТЬ РАЗЛИЧНЫЕ
ПерваяДуга.НачалоДуги КАК НачалоДуги,
ВтораяДуга.КонецДуги КАК КонецДуги
ПОМЕСТИТЬ ЗамыканияДлины2
ИЗ
ЗамыканияДлины1 КАК ПерваяДуга
ВНУТРЕННЕЕ СОЕДИНЕНИЕ ЗамыканияДлины1 КАК ВтораяДуга
ПО ПерваяДуга.КонецДуги = ВтораяДуга.НачалоДуги
;
//////////////////////////////////////////////////////////// ////////////////////
УНИЧТОЖИТЬ ЗамыканияДлины1
;
//////////////////////////////////////////////////////////// ////////////////////
ВЫБРАТЬ РАЗЛИЧНЫЕ
ПерваяДуга.НачалоДуги КАК НачалоДуги,
ВтораяДуга.КонецДуги КАК КонецДуги
ПОМЕСТИТЬ ЗамыканияДлины4
ИЗ
ЗамыканияДлины2 КАК ПерваяДуга
ВНУТРЕННЕЕ СОЕДИНЕНИЕ ЗамыканияДлины2 КАК ВтораяДуга
ПО ПерваяДуга.КонецДуги = ВтораяДуга.НачалоДуги
;
//////////////////////////////////////////////////////////// ////////////////////
УНИЧТОЖИТЬ ЗамыканияДлины2
;
//////////////////////////////////////////////////////////// ////////////////////
ВЫБРАТЬ РАЗЛИЧНЫЕ
ПерваяДуга.НачалоДуги КАК НачалоДуги,
ВтораяДуга.КонецДуги КАК КонецДуги
ПОМЕСТИТЬ ЗамыканияДлины8
ИЗ
ЗамыканияДлины4 КАК ПерваяДуга
ВНУТРЕННЕЕ СОЕДИНЕНИЕ ЗамыканияДлины4 КАК ВтораяДуга
ПО ПерваяДуга.КонецДуги = ВтораяДуга.НачалоДуги
;
//////////////////////////////////////////////////////////// ////////////////////
УНИЧТОЖИТЬ ЗамыканияДлины4
;
//////////////////////////////////////////////////////////// ////////////////////
ВЫБРАТЬ
ЗамыканияДлины8.НачалоДуги КАК НачалоДуги,
ЗамыканияДлины8.КонецДуги КАК КонецДуги
ПОМЕСТИТЬ ВТ_СкладыДостижимость
ИЗ
ЗамыканияДлины8 КАК ЗамыканияДлины8
ГДЕ
ЗамыканияДлины8.КонецДуги В ИЕРАРХИИ
(ВЫБРАТЬ
ВТ_СтатусыЗакупокПоНоменклатуре.Склад
ИЗ
ВТ_СтатусыЗакупокПоНоменклатуре)
;
//////////////////////////////////////////////////////////// ////////////////////
УНИЧТОЖИТЬ ЗамыканияДлины8
;
//////////////////////////////////////////////////////////// ////////////////////
ВЫБРАТЬ
КОЛИЧЕСТВО(ВТ_СкладыДостижимость.НачалоДуги) - 1 КАК Уровень,
ВТ_СкладыДостижимость.КонецДуги КАК Ссылка
ПОМЕСТИТЬ ВТ_СкладыУровниИерархии
ИЗ
ВТ_СкладыДостижимость КАК ВТ_СкладыДостижимость
СГРУППИРОВАТЬ ПО
ВТ_СкладыДостижимость.КонецДуги
;
//////////////////////////////////////////////////////////// ////////////////////
ВЫБРАТЬ
ВТ_СтатусыЗакупокПоНоменклатуре.Номенклатура КАК Номенклатура,
ВТ_СкладыДостижимость.НачалоДуги КАК НачалоДуги,
ВТ_СкладыДостижимость.КонецДуги КАК КонецДуги,
ВТ_СкладыУровниИерархии_Начало.Уровень КАК УровеньНачалаДуги,
ВТ_СкладыУровниИерархии_Конец.Уровень КАК УровеньКонцаДуги,
ВТ_СкладыУровниИерархии_Конец.Уровень - ВТ_СкладыУровниИерархии_Начало.Уровень КАК Длина,
ВТ_СтатусыЗакупокПоНоменклатуре.Статус КАК Статус
ПОМЕСТИТЬ ВТ_СтатусыЗакупокИДостижимостьСкладов
ИЗ
ВТ_СкладыДостижимость КАК ВТ_СкладыДостижимость
ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТ_СтатусыЗакупокПоНоменклатуре КАК ВТ_СтатусыЗакупокПоНоменклатуре
ПО ВТ_СкладыДостижимость.НачалоДуги = ВТ_СтатусыЗакупокПоНоменклатуре.Склад
ЛЕВОЕ СОЕДИНЕНИЕ ВТ_СкладыУровниИерархии КАК ВТ_СкладыУровниИерархии_Начало
ПО ВТ_СкладыДостижимость.НачалоДуги = ВТ_СкладыУровниИерархии_Начало.Ссылка
ЛЕВОЕ СОЕДИНЕНИЕ ВТ_СкладыУровниИерархии КАК ВТ_СкладыУровниИерархии_Конец
ПО ВТ_СкладыДостижимость.КонецДуги = ВТ_СкладыУровниИерархии_Конец.Ссылка
;
//////////////////////////////////////////////////////////// ////////////////////
ВЫБРАТЬ
ВТ_СтатусыЗакупокИДостижимостьСкладов.Номенклатура КАК Номенклатура,
ВТ_СтатусыЗакупокИДостижимостьСкладов.КонецДуги КАК КонецДуги,
МИНИМУМ(ВТ_СтатусыЗакупокИДостижимостьСкладов.Длина) КАК Длина
ПОМЕСТИТЬ ВТ_СтатусыПоСкладамСМинимальнойДлиной
ИЗ
ВТ_СтатусыЗакупокИДостижимостьСкладов КАК ВТ_СтатусыЗакупокИДостижимостьСкладов
ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТ_Товары КАК ВТ_Товары
ПО ВТ_СтатусыЗакупокИДостижимостьСкладов.Номенклатура = ВТ_Товары.Номенклатура
И ВТ_СтатусыЗакупокИДостижимостьСкладов.КонецДуги = ВТ_Товары.Склад
СГРУППИРОВАТЬ ПО
ВТ_СтатусыЗакупокИДостижимостьСкладов.Номенклатура,
ВТ_СтатусыЗакупокИДостижимостьСкладов.КонецДуги
;
//////////////////////////////////////////////////////////// ////////////////////
ВЫБРАТЬ
ВТ_СтатусыПоСкладамСМинимальнойДлиной.Номенклатура КАК Номенклатура,
ВТ_СтатусыПоСкладамСМинимальнойДлиной.КонецДуги КАК Склад,
ВТ_СтатусыЗакупокИДостижимостьСкладов.Статус КАК Статус
ПОМЕСТИТЬ ВТ_СтатусыИтоговая
ИЗ
ВТ_СтатусыПоСкладамСМинимальнойДлиной КАК ВТ_СтатусыПоСкладамСМинимальнойДлиной
ЛЕВОЕ СОЕДИНЕНИЕ ВТ_СтатусыЗакупокИДостижимостьСкладов КАК ВТ_СтатусыЗакупокИДостижимостьСкладов
ПО ВТ_СтатусыПоСкладамСМинимальнойДлиной.Номенклатура = ВТ_СтатусыЗакупокИДостижимостьСкладов.Номенклатура
И ВТ_СтатусыПоСкладамСМинимальнойДлиной.КонецДуги = ВТ_СтатусыЗакупокИДостижимостьСкладов.КонецДуги
И ВТ_СтатусыПоСкладамСМинимальнойДлиной.Длина = ВТ_СтатусыЗакупокИДостижимостьСкладов.Длина
;
//////////////////////////////////////////////////////////// ////////////////////
ВЫБРАТЬ
ВТ_Товары.НомерСтроки КАК НомерСтроки,
ПРЕДСТАВЛЕНИЕССЫЛКИ(ВТ_Товары.Номенклатура) КАК НоменклатураПредставление,
ПРЕДСТАВЛЕНИЕССЫЛКИ(ВТ_Товары.Склад) КАК СкладПредставление,
ВТ_СтатусыИтоговая.Статус ЕСТЬ НЕ NULL КАК ИмеетсяСтатус,
ВТ_СтатусыИтоговая.Статус КАК Статус
ИЗ
ВТ_Товары КАК ВТ_Товары
ЛЕВОЕ СОЕДИНЕНИЕ ВТ_СтатусыИтоговая КАК ВТ_СтатусыИтоговая
ПО ВТ_Товары.Номенклатура = ВТ_СтатусыИтоговая.Номенклатура
И ВТ_Товары.Склад = ВТ_СтатусыИтоговая.Склад
ГДЕ
&ВыражениеОграничений
ПоказатьДопустил, что уровень складов не более 8, поэтому 4 соединения/замыкания достаточно.
Также мне понадобилась длина/путь из "начала в конец дуги", т.к. по условию нужно брать ближайший склад, а по одному уровню вложенности его не определить.
Как считаете, не намудрил ли я тут?
PS: кстати, запрос из статьи с АВТОУРОВЕНЬЗАПИСИ() не работает на пострги из-за соединения.
По реализации при беглом взгляде кажется, что можно было сделать проще. Ведь нужно найти не перекрытый статус. Для этого уровень как будто бы вообще не нужен. Достаточно найти статус на складе в начале дуги и убедиться, что между ним и складом в заказе нет заданных статусов. Это три таблицы с двумя соединениями.
Также не нравится использование В ИЕРАРХИИ. Если уже получено транзитивное замыкание подчинения складов, то можно обойтись без этого.
Но могу ошибаться, так как для полной уверенности мне нужно было бы написать этот запрос самому...
Все верно!
(128)
Не смог от неё избавится. Логично, что вся иерархия уже есть, но я не смог получить результат ВТ_СкладыДостижимость (скрин "Достижимость") без оператора В ИЕРАРХИИ т.к. последняя запись (_Основной Ромашка 2, Основной Ромашка 2) пропадает. И по замеру пакета запросов это самое узкое место.
Кажется, что такого запроса достаточно, но я не все сценарии прошёл:
ВЫБРАТЬ
ЗамыканияДлины8.НачалоДуги КАК НачалоДуги,
ЗамыканияДлины8.КонецДуги КАК КонецДуги
ПОМЕСТИТЬ ВТ_СкладыДостижимость
ИЗ
ЗамыканияДлины8 КАК ЗамыканияДлины8
ГДЕ
ЗамыканияДлины8.НачалоДуги В
(ВЫБРАТЬ
ВТ_СтатусыЗакупокПоНоменклатуре.Склад
ИЗ
ВТ_СтатусыЗакупокПоНоменклатуре)
Показать(128)
Звучит убедительно, но реализация мне не по зубам)
Вы можете заказать платную консультацию или разработку у автора. Будет создан приватный заказ на «Бирже заказов» для автора.
№ 160707
Создание 13.11.12 18:57
Обновление 28.07.20 09:35
Просмотры 159493
Загрузки 340
Рейтинг
470
Комментарии 107
Код открыт Да
Рубрики Запросы
Кому Разработчик
Тип файла Внешний отчет (ert,erf)
Платформа 1С:Предприятие 8
Конфигурация 1C:Бухгалтерия
Операционная система Windows
Страна Не имеет значения
Отрасль Не имеет значения
Налоги Не имеет значения
Вид учета Не имеет значения
Доступ к файлу Абонемент ($m)