Иерархическая сумма Oracle (расстояние от листа до корня) - PullRequest
0 голосов
/ 08 декабря 2018

Я хотел бы получить помощь для иерархического запроса (Oracle 11gR2).Мне трудно с такими запросами ...

На самом деле, это вопрос 2 в 1 (требуется 2 разных подхода).

Я ищу способ получитьрасстояние от всех записей индивидов до корня (не наоборот).Мои данные имеют древовидную структуру:

CREATE TABLE MY_TREE
(ID_NAME VARCHAR2(1) PRIMARY KEY,
 PARENT_ID VARCHAR2(1),
 PARENT_DISTANCE NUMBER(2)
);

INSERT INTO MY_TREE (ID_NAME,PARENT_ID,PARENT_DISTANCE) VALUES('A',NULL,NULL);
INSERT INTO MY_TREE (ID_NAME,PARENT_ID,PARENT_DISTANCE) VALUES('B','A',1);
INSERT INTO MY_TREE (ID_NAME,PARENT_ID,PARENT_DISTANCE) VALUES('C','B',3);
INSERT INTO MY_TREE (ID_NAME,PARENT_ID,PARENT_DISTANCE) VALUES('D','B',5);
INSERT INTO MY_TREE (ID_NAME,PARENT_ID,PARENT_DISTANCE) VALUES('E','C',7);
INSERT INTO MY_TREE (ID_NAME,PARENT_ID,PARENT_DISTANCE) VALUES('F','D',11);
INSERT INTO MY_TREE (ID_NAME,PARENT_ID,PARENT_DISTANCE) VALUES('G','D',13);

Иерархически мои данные выглядят так (но у меня несколько независимых корней и много других уровней):

enter image description here

При первом приближении я ищу запрос, который даст мне такой результат:

LEVEL ROOT NODE ID_NAME ROOT_DISTANCE
----- ---- ---- ------- -------------
1     A    null A       null
2     A    null B       1
3     A    B    C       4
4     A    B    E       11
3     A    B    D       6
4     A    D    F       17
4     A    D    G       19

В этом результате

  • столбец «NODE» означает ID_NAME ближайшего элемента разбиения
  • , столбец «ROOT_DISTANCE» означает расстояние от элемента до корня (например: ROOT_DISTANCE для ID_NAME = G - это расстояние от G до A:G (13) + D (5) + B (1) = 19)

В этом приближении я всегда буду указывать максимум 2 корня.

Второй подход долженбыть сценарием PL / SQL, который будет выполнять те же вычисления (ROOT_DISTANCE), но итеративным способом, и записывать результат в новую таблицу.Я хочу запустить этот скрипт один раз, поэтому все корни (~ 1000) будут обработаны.

Вот как я вижу скрипт:

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

Этот сценарий необходим для «перспектив производительности», поэтому, если элемент уже был рассчитан (например, разделенный узел, который был рассчитан другим листом), нам нужно остановить вычисление и перейти кследующий лист, потому что мы уже знаем результат оттуда до корня.Например, если система вычисляет ECBA, а затем FDBA, раздел BA не должен быть снова рассчитан, поскольку это было сделано при первом проходе.

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

Спасибо!

Ответы [ 3 ]

0 голосов
/ 08 декабря 2018

Попробуйте это:

WITH brumba(le_vel,root,node,id_name,root_distance) AS (
  SELECT 1 as le_vel, id_name as root, null as node, id_name, to_number(null) as root_distance  
  FROM MY_TREE WHERE parent_id IS NULL
  UNION ALL
  SELECT b.le_vel + 1, b.root, 
         CASE WHEN 1 < (
                SELECT count(*) FROM MY_TREE t1 WHERE t1.parent_id = t.parent_id
              )
              THEN t.parent_id ELSE b.node
         END,
         t.id_name, coalesce(b.root_distance,0)+t.parent_distance
  FROM MY_TREE t
  JOIN brumba b ON b.id_name = t.parent_id
)
SELECT * FROM brumba

Демонстрация: https://dbfiddle.uk/?rdbms=oracle_11.2&fiddle=d5c231055e989c3cbcd763f4b3d3033f


Нет необходимости во "втором подходе" с использованием PL / SQL - приведенный выше SQL рассчитаетрезультаты сразу для всех корневых узлов (которые имеют ноль в столбце parent_id).
Просто добавьте префикс INSERT INTO tablename(col1,col2, ... colN) ... или CREATE TABLE name AS ... к вышеуказанному запросу.
Приведенная выше демонстрация содержит пример для последнегоопция CREATE TABLE xxx AS query

0 голосов
/ 11 декабря 2018

Вот как вы можете решить эту проблему с помощью иерархического (connect by) запроса.

В большинстве иерархических задач иерархические запросы будут выполняться быстрее, чем рекурсивные (пункт рекурсивный with).Однако ваш вопрос не является чисто иерархическим - вам нужно вычислить расстояние до корня, и в отличие от рекурсивного with, вы не можете получить это за один проход с иерархическими запросами.Так что будет интересно услышать - от вас!- существуют ли какие-либо существенные различия в производительности между подходами.На самом деле, для предоставленной вами очень небольшой выборки данных Оптимизатор оценил стоимость 5, используя connect by, против 48 для рекурсивного решения;Означает ли это что-то в реальной жизни, вы узнаете, и, надеюсь, вы тоже дадите нам знать.

В иерархическом запросе я выделяю родителей, у которых двое или более детей (я использую аналитическую функцию дляэто, чтобы избежать объединения).Затем я строю иерархию и на последнем шаге собираю все необходимые биты.Как и в рекурсивном решении, это должно дать вам все, что вам нужно - для всех корней и всех узлов - в одном запросе SQL;в PL / SQL-коде нет необходимости.

with
  branching (id_name, parent_id, parent_distance, b) as (
    select id_name, parent_id, parent_distance, 
           case when count(*) over (partition by parent_id) > 1 then 'y' end
    from   my_tree
  )
, hierarchy (lvl, leaf, id_name, parent_id, parent_distance, b) as (
    select  level, connect_by_root id_name, id_name, parent_id, parent_distance, b
    from    branching
    connect by id_name = prior parent_id
  )
select   max(lvl) as lvl, 
         min(id_name) keep (dense_rank last order by lvl) as root,
         leaf as id_name,
         min(decode(b, 'y', parent_id)) 
           keep (dense_rank first order by decode(b, 'y', lvl)) as node,
         sum(parent_distance) as root_distance
from     hierarchy
group by leaf;

LVL ROOT    ID_NAME NODE    ROOT_DISTANCE
--- ------- ------- ------- -------------
  1 A       A                            
  2 A       B                           1
  3 A       C       B                   4
  3 A       D       B                   6
  4 A       E       B                  11
  4 A       F       D                  17
  4 A       G       D                  19
0 голосов
/ 08 декабря 2018

Вот один вариант, который показывает, как получить первую часть вашего вопроса:

SQL> with temp as
  2    (select level lvl,
  3            ltrim(sys_connect_by_path(id_name, ','), ',') path
  4     from my_tree
  5     start with parent_id is null
  6     connect by prior id_name = parent_id
  7    ),
  8  inter as
  9    (select t.lvl,
 10            t.path,
 11            regexp_substr(t.path, '[^,]+', 1, column_value) col
 12     from temp t,
 13          table(cast(multiset(select level from dual
 14                              connect by level <= regexp_count(path, ',') + 1
 15                             ) as sys.odcinumberlist ))
 16    )
 17  select i.lvl,
 18         i.path,
 19         sum(m.parent_distance) dis
 20  from inter i join my_tree m on m.id_name = i.col
 21  group by i.lvl, i.path
 22  order by i.path;

 LVL PATH              DIS
---- ---------- ----------
   1 A
   2 A,B                 1
   3 A,B,C               4
   4 A,B,C,E            11
   3 A,B,D               6
   4 A,B,D,F            17
   4 A,B,D,G            19

7 rows selected.

SQL>
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...