Гость
Форумы / Oracle [игнор отключен] [закрыт для гостей] / Разбить направленный граф на несколько деревьев / 16 сообщений из 16, страница 1 из 1
06.09.2019, 19:01
    #39858615
ln123
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Разбить направленный граф на несколько деревьев
Добрый день, столкнулся со следующей задачей.
Есть таблица tPart в которой хранятся партии изготавливаемых изделий, для изготовления партии изделий используются другие партии изделий, какая партия в какую входит описано с помощью таблицы tLink.
Мне нужно построить дерево вхождений партий так что бы в корневую партию с минимальным номером входили листовые партии так же с минимальным номером (id).

Пример данных:
Код: plsql
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
with tPart (id , fQty) as (select 1, 1 from dual
union all
select 2, 1 from dual
union all
select 3, 1 from dual
union all
select 4, 3 from dual
union all
select 5, 1 from dual
union all
select 6, 2 from dual
union all
select 7, 1 from dual
union all
select 8, 2 from dual
union all
select 9, 1 from dual
union all
select 10, 1 from dual
),
tLink (idParent, idChild, fQty) as (select 1 , 4 , 1 from dual
union all 
select 2 , 4 , 1 from dual
union all
select 3 , 4 , 1 from dual
union all
select 4 , 5 , 1 from dual
union all 
select 4 , 6 , 2 from dual
union all
select 6 , 9 , 1 from dual
union all
select 6 , 10 , 1 from dual)

select * from ....



Желаемый результат:

idRoot idParent idChild fQty1 1 4 11 4 5 12 3 4 12 4 6 12 6 9 13 3 4 13 4 6 13 6 10 1

Вопрос можно ли данную задачу решить на SQL (на PL/SQL у меня решение есть, но мне оно не совсем нравится) без использования model?
...
Рейтинг: 0 / 0
06.09.2019, 19:21
    #39858621
Кобанчег
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Разбить направленный граф на несколько деревьев
ln123,

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

Почему (4,5) относится к корню 1, а (4,6) к корню 2?
Почему (4,6) фигурирует в результате дважды?
Почему у (4,6) fQty в результате стало 1 а не 2 и нужно ли оно вообще или это мусор не имеющий отношения к постановке?
Нужна ли таблица tPart для решения или это просто список айдишников?
И т. д.

Код: plsql
1.
2.
3.
4.
5.
6.
select min(connect_by_root idParent) root, t.*
from tLink t
connect by prior idChild = idParent
group by idParent, idChild, fQty
order by 1, 2, 3
/



Код: plsql
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
      ROOT   IDPARENT    IDCHILD       FQTY
---------- ---------- ---------- ----------
         1          1          4          1
         1          4          5          1
         1          4          6          2
         1          6          9          1
         1          6         10          1
         2          2          4          1
         3          3          4          1

7 rows selected.
...
Рейтинг: 0 / 0
07.09.2019, 10:30
    #39858700
ln123
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Разбить направленный граф на несколько деревьев
Кобанчегln123,

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

Почему (4,5) относится к корню 1, а (4,6) к корню 2?


Потому что по условию задачи "в корневую партию с минимальным номером входили листовые партии так же с минимальным номером" таким образом получается что 1<2 поэтому его нужно обеспечивать в первую очередь 5<9<10 (5,9,10 это в данном случае листья) поэтому 5-тая партия входит в 1-ю а 9-я во вторую (через 6-ю)

Почему (4,6) фигурирует в результате дважды?


Потому что в одном случае это часть пути от 2 до 9, а в другом от 3 до 10.

Почему у (4,6) fQty в результате стало 1 а не 2 и нужно ли оно вообще или это мусор не имеющий отношения к постановке?
Нужна ли таблица tPart для решения или это просто список айдишников?


fQty - это количество, в tPart - это количество в партии, а в tLink количество в связи.
Таким образом, данные нужно понимать так, партия 4 состоит из 3-х изделий каждое из изделий может быть изготовлено или из 1 шт партии 5 или из 1 шт партии 6. (3 (партия 4)= 1 ( партия 5) + 2 (партия 6).
Исходя из вышеизложенного для изготовления партии 2 нужна 1 шт партии 4, а для изготовления 1 шт партии 4 нужно либо 1 шт партии 5, но она уже занята под партию 1 либо 1 шт партии 6, в результате получаем искомую запись idRoot = 2, idParent=4, idChild = 6, fQty=1.
...
Рейтинг: 0 / 0
10.09.2019, 08:34
    #39859666
Кобанчег
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Разбить направленный граф на несколько деревьев
ln123Вопрос можно ли данную задачу решить на SQL (на PL/SQL у меня решение есть, но мне оно не совсем нравится) без использования model?Можно решить с использованием временной таблицы и весьма симпатично.
...
Рейтинг: 0 / 0
10.09.2019, 21:05
    #39860042
ln123
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Разбить направленный граф на несколько деревьев
Кобанчег,

Ну вы прям заинтриговали :),
я сейчас делаю то же с помощью временной таблицы, но свое решение я бы симпатичным не назвал.
Выглядит приблизительно так:
1. В таблицу закидываем все возможные пути от корня до листьев (получаются простым иерархическим запросом при спуске вниз по дереву)
2. Идем циклом (PL/SQL) по записям отсортированным по idRoot, id листа
Если у данной записи есть в пути развилки/альтернативы то пересчитываем количество в альтернативных ветках, если количество получилось равным 0, то удаляем такую запись. Помечаем текущую запись как обработанную, переходим на следующую по порядку.
Когда пройдем все записи то в таблице останется искомый результат.

А у вас какой вариант был?
...
Рейтинг: 0 / 0
11.09.2019, 00:03
    #39860064
Кобанчег
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Разбить направленный граф на несколько деревьев
ln123,

У тебя чудесным образом строятся не деревья, а просто списки от каждого корня до некоторого листа.
При этом на каждом шаге выбирается ребро с минимальным ребенком.
Если fQty определяет сколько раз может быть использовано ребро, то можно как-то так:

Код: plsql
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
SQL> create global temporary table tmp_tLink (idParent int, idChild int, fQty int);

Table created.

SQL> create unique index uk_tmp_tLink on tmp_tLink(idParent, idChild);

Index created.

SQL> create global temporary table result (idParent int, idChild int, root int);

Table created.

SQL>
SQL> insert into tmp_tLink select * from tLink;

7 rows created.

SQL>
SQL> declare
  2    prnt int;
  3    chld int;
  4  begin
  5
  6    for i in (select idParent
  7                from tLink t
  8               where not exists
  9               (select '' from tLink t0 where t0.idChild = t.idParent)) loop
 10
 11      prnt := i.idParent;
 12
 13      -- строим путь из очередного корня
 14      while true loop
 15
 16        select min(idChild) into chld from tmp_tLink where idParent = prnt;
 17
 18        if chld is null then
 19          exit;
 20        else
 21          insert into result values (prnt, chld, i.idparent);
 22          prnt := chld;
 23        end if;
 24
 25      end loop;
 26
 27      -- обновляем использованные ребра
 28      merge into tmp_tLink t
 29      using (select * from result where root = i.idparent) r
 30      on (t.idParent = r.idParent and t.idChild = r.idChild)
 31      when matched then update set t.fQty = t.fQty - 1 delete where t.fQty = 0;
 32
 33    end loop;
 34
 35  end;
 36  /

PL/SQL procedure successfully completed.

SQL>
SQL> select * from result;

  IDPARENT    IDCHILD       ROOT
---------- ---------- ----------
         1          4          1
         4          5          1
         2          4          2
         4          6          2
         6          9          2
         3          4          3
         4          6          3
         6         10          3

8 rows selected.

Предполагается, что в выделенной строке в твоём результате опечатка.

PS. Case sensitive Java style identifiers в Оракле это мерзко.
...
Рейтинг: 0 / 0
11.09.2019, 21:12
    #39860560
ln123
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Разбить направленный граф на несколько деревьев
Кобанчегln123,

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


Пример несколько упрощен, но да же в этой упрощенной структуре при других данных легко можно получить дерево.
Например:
Код: plsql
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
with tPart (id , fQty) as (select 1, 1 from dual
union all
select 2, 1 from dual
union all
select 3, 1 from dual
union all
select 4, 4 from dual
union all
select 5, 1 from dual
union all
select 6, 3 from dual
union all
select 9, 2 from dual
union all
select 10, 1 from dual
),
tLink (idParent, idChild, fQty) as (select 1 , 4 , 2 from dual
union all 
select 2 , 4 , 1 from dual
union all
select 3 , 4 , 1 from dual
union all
select 4 , 5 , 1 from dual
union all 
select 4 , 6 , 3 from dual
union all
select 6 , 9 , 2 from dual
union all
select 6 , 10 , 1 from dual)



Для первой партии даст результат
idRoot idParent idChild fQty1 1 4 21 4 5 11 4 6 11 6 9 1

При этом на каждом шаге выбирается ребро с минимальным ребенком.


Нет гарантии что выбор минимального ребенка на промежуточном шаге приведет в конечном счете к минимальному листу.
Хотя наверное данную проблему можно решить предварительной подготовкой данных, пройдясь от листьев вверх и выполнив перенумерацию промежуточных узлов.
...
Рейтинг: 0 / 0
12.09.2019, 13:34
    #39860905
Кобанчег
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Разбить направленный граф на несколько деревьев
ln123Нет гарантии что выбор минимального ребенка на промежуточном шаге приведет в конечном счете к минимальному листу.Иными словами можно стоить дерево из каждого корня и потом отбирать листья (и все промежуточные ребра) покуда qty в каждом узле пути не исчерпано?

Глубина не важна?
То есть, если в дереве ниже везде qty=1, то отбирается только ребро 5-0, потому что 0 - минимальный лист?

Код: plaintext
1.
2.
3.
      5
    3/ \0
  6/ \7
1/
...
Рейтинг: 0 / 0
12.09.2019, 20:54
    #39861174
ln123
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Разбить направленный граф на несколько деревьев
Кобанчег,

Да, все верно.
...
Рейтинг: 0 / 0
13.09.2019, 17:54
    #39861733
ln123
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Разбить направленный граф на несколько деревьев
Кстати мое решение данной задачи выглядит вот так

Код: plsql
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
100.
101.
102.
103.
104.
105.
106.
107.
108.
109.
110.
111.
112.
113.
114.
115.
116.
117.
118.
insert into tmp_link
with tPart (id , fQty) as (select 1, 1 from dual
union all
select 2, 1 from dual
union all
select 3, 1 from dual
union all
select 4, 3 from dual
union all
select 5, 1 from dual
union all
select 6, 2 from dual
union all
select 7, 1 from dual
union all
select 8, 2 from dual
union all
select 9, 1 from dual
union all
select 10, 1 from dual
),
tLink (idParent, idChild, fQty) as (select 1 , 4 , 1 from dual
union all 
select 2 , 4 , 1 from dual
union all
select 3 , 4 , 1 from dual
union all
select 4 , 5 , 1 from dual
union all 
select 4 , 6 , 2 from dual
union all
select 6 , 9 , 1 from dual
union all
select 6 , 10 , 1 from dual),
t (id, fqty, fqtyAll, sPath, bProcessed, idParent, idRoot, nlvl, fqtylink) as 
(select idChild, fQty, fQty, '-'||tlink.idParent, 0, idParent, idParent, 1, fQty  from tLink where idParent not in (select idChild from tLink)
union all
select tLink.idChild, least(t.fQty, tLink.fqty) , t.fQty, t.sPath||'-'||tLink.idParent, 0, tLink.idParent, t.idRoot, t.nlvl+1, tLink.fQty  from tLink, t where t.id = tLink.idParent
)
select t.*, row_number() over(order by t.idRoot, t.nlvl, t.id) from t; 
/
declare
  nvRn number(15) := 0;
  svPath varchar2(4000);
  svSubPath varchar2(4000);
  svPrevSubPath varchar2(4000);
  i number; 
  fvQty number;
  fvQtyAll number;
  idv number;
  idvParent number;
  
begin
loop
  --выбираем первую по порядку не обработанную запись
  select min(nrn) 
  into nvRn 
  from tmp_link 
  where id not in (select idParent from tmp_link where idParent is not null)
  and bProcessed = 0
  and nrn > nvRn;

  exit when nvRn is null;
  --dbms_application_info.set_action(nvRn);
  select sPath, fQty, id, idParent, fQtyAll
   into svPath, fvQty, idv, idvParent, fvQtyAll
  from tmp_link where nrn = nvRn;
  
  update tmp_link set bProcessed = 1 where nrn = nvRn;
  --обновляем количество в не обработанных записях
  --для каждого узла проверяем наличие ветвлений
  i := 1;
  svSubPath := svPath;
  svPrevSubPath := '!!!';
  loop
    --if instr(svSubPath,'p') > 0 then --альтернативы есть только на укрупненных позициях
      --обновляем свои альтернативы
      update tmp_link  t3 
      set fqtyAll = least(fqtyAll,fvQtyAll  - fvQty), fqty = least(fqty, fvqtyAll - fvQty)
      where  t3.sPath like svSubPath||'%'
      and t3.sPath not like svPrevSubPath||'%' --запрет на спуск тем же путем которым мы поднимаемся    
      and bProcessed = 0;
      
            
      --обновляем чужие альтернативы
      update tmp_link  t3 
      set fqty = least(fqty, t3.fqtylink - fvQty)
      where  t3.idParent = idvParent 
      and t3.sPath != svSubPath 
      and t3.id = idv
      and bProcessed = 0;
      --удаляем лишнее
      delete from tmp_link  t3 
      where 
      (t3.idParent = idvParent or t3.sPath like svSubPath||'%')  
      and (t3.fqty = 0 or t3.fqtyAll = 0) 
      and bProcessed = 0;
    --end if;  
    --сдвигаемся выше по дереву
    exit when idvParent is null;
    svPrevSubPath := svSubPath; 
    svSubPath := substr(svSubPath,1,instr(svSubPath,'-',-1)-1);
    
    begin
      select sPath, fQty, id, idParent, fQtyAll
      into svPath, fvQty, idv, idvParent, fvQtyAll
      from tmp_link where sPath = svSubPath and id = idvParent;
    exception
      when no_data_found then
        exit;    
    end;
    update tmp_link set bProcessed = 1 where sPath = svSubPath and id = idv;
  end loop;
  
end loop; 
end; 
/
select idRoot, idParent, id, fqty from tmp_link;



Но во первых это не красиво :), а во вторых не очень быстро.
На тестовом примере с количеством связей порядка 43 тыс., мой код работает 15-20 минут (с учетом наличия правильных индексов на промежуточной таблице).
Конечно его можно оптимизировать обработав то что все эти сложности нужны только если есть ветвления, убрать delete из цикла и т.п., но когда писал на форму я очень надеялся что кто нибудь предложит какой-нибудь альтернативный подход.
...
Рейтинг: 0 / 0
13.09.2019, 19:37
    #39861780
Elic
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Разбить направленный граф на несколько деревьев
ln123я очень надеялся что кто нибудь предложит какой-нибудь альтернативный подход.Т.е. премию надеялся пропить самолично, наглая сволочь?
...
Рейтинг: 0 / 0
13.09.2019, 19:43
    #39861783
Кобанчег
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Разбить направленный граф на несколько деревьев
ln123,

Какое число корней?

По сути необходимо для каждого корня делать следующее:
1) строим дерево
2) отсекаем не подходящие пути
3) отмечаем использованные ребра

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

У тебя операторов поболее и если честно мне в тот код вникать лень.
...
Рейтинг: 0 / 0
14.09.2019, 10:34
    #39861872
ln123
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Разбить направленный граф на несколько деревьев
Elic,

Не стоит всех равнять по себе, премию я уже две недели назад как пропил :)
...
Рейтинг: 0 / 0
14.09.2019, 11:14
    #39861875
ln123
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Разбить направленный граф на несколько деревьев
Кобанчегln123,

Какое число корней?


На ограниченной выборке было 5-8, на полной меньше 100.

По сути необходимо для каждого корня делать следующее:
1) строим дерево


Ок, это я сделал.

2) отсекаем не подходящие пути
3) отмечаем использованные ребра


Не вижу смысла разбивать на два шага, т.к. мы сначала выбираем подходящий путь и соответственно можем пометить использованные ребра, а затем уже можем отсекать не подходящие пути.
Но тут то и кроется проблема, что бы просто отметить ребра как использованные нужно подняться вверх от каждого еще не отсеченного листа, что бы отсечь не подходящие пути нужно спуститься вниз от каждой развилки пересчитав доступное количество.
Как это сделать одним оператором я не придумал :(

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


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

У тебя операторов поболее и если честно мне в тот код вникать лень.

Код плохой (это просто упрощещенная иллюстрация возможного решения) и особого смысла в него вникать нет.
Мне было бы интересно увидеть как такую задачу можно решить только на SQL, либо узнать что для ее решения есть какой нибудь известный алгоритм (по идеи задача довольно типовая и общая).
В любом случае спасибо за диалог.
...
Рейтинг: 0 / 0
18.09.2019, 15:01
    #39863355
Кобанчег
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Разбить направленный граф на несколько деревьев
ln123Пример несколько упрощенНадо было, кстати, дальше доупрощать и выкинуть таблицу tPart.
Её необходимость для решения так и осталась неясна.
ln123Не вижу смысла разбивать на два шага, т.к. мы сначала выбираем подходящий путь и соответственно можем пометить использованные ребра, а затем уже можем отсекать не подходящие пути.Под "3) отмечаем использованные ребра" подразумевался merge statement в моём примере,
то есть обновляется исходный граф чтоб при построении дерева из следующего корня в нём было учтено что уже использовано.
ln123Но тут то и кроется проблема, что бы просто отметить ребра как использованные нужно подняться вверх от каждого еще не отсеченного листа, что бы отсечь не подходящие пути нужно спуститься вниз от каждой развилки пересчитав доступное количество.
Как это сделать одним оператором я не придумал :(Если б ты изначально отбросил ненужное и спросил как решить это одним оператором, возможно, отвечающих было бы больше.
Код: plsql
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
select idparent,
       idchild,
       count(*) used_qty
  from (select t.*,
               min(fqty) over (partition by connect_by_root idchild) min_qty,
               dense_rank() over (order by connect_by_root idchild) drnk
          from (select t.*, connect_by_isleaf is_leaf
                  from tlink t
                start with idparent = 1
                connect by prior idchild = idparent) t
        start with is_leaf = 1
        connect by prior idparent = idchild) t0
 where min_qty >= drnk
group by idparent, idchild
order by 1, 2


Итого (если есть острое желание можно всю логику засунуть в merge, но понадобится дополнительное теложвижение, чтоб построить итоговый результат):
Код: plsql
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
create global temporary table tmp_tLink (idParent int, idChild int, fQty int);
create unique index uk_tmp_tLink on tmp_tLink(idParent, idChild);
create global temporary table result (root int, idParent int, idChild int, fQty int);
insert into tmp_tlink select * from tlink;

begin

   for i in (select idparent
               from tlink t
              where not exists
              (select '' from tlink t0 where t0.idchild = t.idparent)
              order by 1)
   loop

      -- 1) строим дерево + 2) отсекаем не подходящие пути
      insert into result(root, idparent, idchild, fqty)
         select i.idparent,
                idparent,
                idchild,
                count(*) used_qty
           from (select t.*,
                        min(fqty) over (partition by connect_by_root idchild) min_qty,
                        dense_rank() over (order by connect_by_root idchild) drnk
                   from (select t.*, connect_by_isleaf is_leaf
                           from tmp_tlink t
                         start with idparent = i.idparent
                         connect by prior idchild = idparent) t
                 start with is_leaf = 1
                 connect by prior idparent = idchild) t0
          where min_qty >= drnk
         group by idparent, idchild;

      -- 3) обновляем использованные ребра
      merge into tmp_tLink t
      using (select * from result where root = i.idparent) r
      on (t.idParent = r.idParent and t.idChild = r.idChild)
      when matched then update set t.fQty = t.fQty - 1 delete where t.fQty = 0;

   end loop;

end;
/

select * from result order by 1, 2, 3;


Код: plsql
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
      ROOT   IDPARENT    IDCHILD       FQTY
---------- ---------- ---------- ----------
         1          1          4          2
         1          4          5          1
         1          4          6          1
         1          6          9          1
         2          2          4          1
         2          4          6          1
         2          6          9          1
         3          3          4          1
         3          4          6          1
         3          6         10          1

10 rows selected.
...
Рейтинг: 0 / 0
18.09.2019, 19:43
    #39863555
Кобанчег
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Разбить направленный граф на несколько деревьев
С аналитикой, конечно, оно в общем случае не вернет корректный результат...

Отсечение можно сделать с еще одной временной таблицей
Код: plsql
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
-- create global temporary table tmp_result (child_root int, idParent int, idChild int, fQty int, is_used int);

insert into tmp_tlink select * from tlink;

begin

   for i in (select idparent
               from tlink t
              where not exists
              (select '' from tlink t0 where t0.idchild = t.idparent) -- and idparent = 1
              order by 1)
   loop

      delete from tmp_result;   
   
      -- 1) строим дерево
      insert into tmp_result(child_root,
                             idparent,
                             idchild,
                             fqty)
         select connect_by_root idchild child_root,
                idparent,
                idchild,
                fqty
           from (select t.*, connect_by_isleaf is_leaf
                   from tmp_tlink t
                 start with idparent = i.idparent
                 connect by prior idchild = idparent) t
         start with is_leaf = 1
         connect by prior idparent = idchild;

      -- 2) отсекаем не подходящие пути
      for j in (select distinct child_root from tmp_result)
      loop

         for dummy in (select min(fqty)
                         from tmp_result
                        where child_root = j.child_root
                       having min(fqty) > 0)
         loop

            merge into tmp_result t
            using (select * from tmp_result where child_root = j.child_root) r
            on (t.idparent = r.idparent and t.idchild = r.idchild)
            when matched then update set t.fqty = t.fqty - 1 where t.child_root > j.child_root;
            
            update tmp_result set is_used = 1 where child_root = j.child_root;

         end loop;

      end loop;
      
      -- 2') добавляем использованные ребра в результат
      insert into result
         select i.idparent,
                idparent,
                idchild,
                count(*) used_qty
           from tmp_result
          where is_used = 1 
         group by idparent, idchild;

      -- 3) обновляем использованные ребра
      merge into tmp_tLink t
      using (select * from result where root = i.idparent) r
      on (t.idParent = r.idParent and t.idChild = r.idChild)
      when matched then update set t.fQty = t.fQty - r.fQty delete where t.fQty = 0;

   end loop;

end;
/

select * from result order by 1, 2, 3;

...
Рейтинг: 0 / 0
Форумы / Oracle [игнор отключен] [закрыт для гостей] / Разбить направленный граф на несколько деревьев / 16 сообщений из 16, страница 1 из 1
Целевая тема:
Создать новую тему:
Автор:
Найденые пользователи ...
Разблокировать пользователей ...
Читали форум (0):
Пользователи онлайн (0):
x
x
Закрыть


Просмотр
0 / 0
Close
Debug Console [Select Text]