Preordered tree traversal: conversion from adjency list
08/01/21 07:23
Debate over usage of agency list or preordered tree traversal are numerous,
we don't enter in pros and cons of each one here, we just provide a recursive procedure
to move from agency list to PTT representation and a query using the MODEL clause providing the same functionality.
we don't enter in pros and cons of each one here, we just provide a recursive procedure
to move from agency list to PTT representation and a query using the MODEL clause providing the same functionality.
Here is a recursive procedure to convert an agency list model to a PTT one,
assuming we will add the left, right, level and sibling index information (adapt to your needs)
assuming we will add the left, right, level and sibling index information (adapt to your needs)
CREATE OR REPLACE PROCEDURE INIT_PTT
IS
r NUMBER(10,0) := 0 ;
l NUMBER(10,0) := 1 ;
PROCEDURE branches(lvl IN NUMBER, p_id IN NUMBER, lft IN NUMBER, rgt OUT NUMBER)
IS
l1 NUMBER(10,0) := lft;
lvl1 NUMBER(10,0) := lvl+1;
idx NUMBER(10,0) := 0 ;
BEGIN
rgt := lft + 1 ;
FOR rec IN (SELECT id, parent_id, name FROM t_data WHERE parent_id = p_id ORDER BY name)
LOOP
branches(lvl1, rec.id, l1+1, rgt) ;
UPDATE t_data set lvl = lvl1, left = (l1+1), right = rgt, sibling_idx = idx WHERE id = rec.id ;
idx := idx + 1;
l1 := rgt ;
rgt := rgt + 1;
END LOOP ;
END ;
BEGIN
FOR rec IN (SELECT id FROM t_data WHERE parent_id is NULL ORDER BY name)
LOOP
branches(0, rec.id, l, r) ;
UPDATE t_data set lvl = 0, left = l, right = r, sibling_idx = 0 WHERE id = rec.id ;
l := r + 1 ;
END LOOP ;
COMMIT ;
END ;
Now the MODEL-based query returning the left, right, level and sibling index values.WITH hdata(lvl, id, name, parent_id, root_id) AS (
select 0, id, name, parent_id, id from t_data d
where parent_id is null
union all
select h.lvl+1, d.id, d.name, d.parent_id, h.root_id
from t_data d
join hdata h on d.parent_id = h.id
)
SEARCH DEPTH FIRST BY parent_id SET rn,
sdata(lvl, id, name, parent_id, root_id) as (
select 0, id, name, parent_id, id from t_data d
union all
select h.lvl+1, d.id, d.name, d.parent_id, h.root_id
from t_data d
join sdata h on d.parent_id = h.id
),
summing as(
select d.lvl, d.id, d.name, d.parent_id, h.cnt
from hdata d
join (
select root_id, count(id)-1 as cnt from sdata
group by root_id
)
h on d.id = h.root_id
),
precalc as (
select s.*, h.rn, lag(s.lvl,1) over(order by h.rn) as prev_lvl,
(select count(s.id) from hdata h1 where h1.lvl = h.lvl and h1.rn < h.rn and (h1.parent_id = h.parent_id
or (h1.parent_id is null and h.parent_id is null))
) as n_leftbrothers,
lag(h.rn,1) over(partition by s.lvl, s.parent_id order by h.rn) as prev_brother_rn
from summing s
join hdata h on h.id = s.id
)
select p.rn, p.id, p.name, p.parent_id, p.left, p.right, p.lvl, p.sibling_idx, p.done, p.hasrun, p.lim,d.*
from (
select rn, id, name, parent_id, left, right, lvl, sibling_idx, done, hasrun, lim
from precalc
model
dimension by(rn)
measures(id as id, name as name, cast(null as number) as left, cast(null as number) as right, 0 as up, lvl as lvl, prev_lvl as prev_lvl,
n_leftbrothers,
cnt as cnt, prev_brother_rn as prev_brother_rn, parent_id as parent_id,
0 as sibling_idx,
0 as done, 0 as hasrun, (select max(rn) from precalc) as lim)
rules
ITERATE(4294967295) UNTIL( hasrun[ITERATION_NUMBER+1] = lim[1])
(
up[ANY] = case when prev_lvl[cv()] > lvl[cv()] or up[cv()-1] = 1 then 1 else 0 end,
-- first root
left[1] = 1,
right[1] = 2*cnt[1]+2,
sibling_idx[1] = 0,
-- others
left[rn > 1] order by rn asc =
case when lvl[cv()] = 0 then right[prev_brother_rn[cv()]] + 1
else
case when prev_lvl[cv()] < lvl[cv()] then
-- we are going down
case when n_leftbrothers[cv()] = 0 then
-- we are the leftmost child of our father
case when up[cv()] = 0 then
-- our father is also the leftmost
cv(rn)
else
left[cv()-1] + 1
end
else
-- this should move to the next left because depends on the next right
right[prev_brother_rn[cv()]]
end
when prev_lvl[cv()] > lvl[cv()] then
-- we are going up
case when n_leftbrothers[cv()] = 0 then
cv(rn) -- we are the leftmost child
else
right[prev_brother_rn[cv()]] + 1
end
else
-- we are going to brother
right[cv()-1] + 1
end
end,
right[rn > 1] order by rn asc =
case when lvl[cv()] = 0 then left[cv()] + 2*cnt[cv()] + 1
else
2*cnt[cv()] + 1 +
case when prev_lvl[cv()] < lvl[cv()] then
-- we are going down
case when n_leftbrothers[cv()] = 0 then
-- we are the leftmost child of our father
case when up[cv()] = 0 then
-- our father is also the leftmost
cv(rn)
else
left[cv()-1] + 1
end
else
-- this should move to the next left because dep
ends on the next right
right[prev_brother_rn[cv()]] end
when prev_lvl[cv()] > lvl[cv()] then
-- we are going up
case when n_leftbrothers[cv()] = 0 then
cv(rn) -- we are the leftmost child
else
right[prev_brother_rn[cv()]] + 1
end
else
-- we are going to brother
right[cv()-1] + 1
end
end,
sibling_idx[rn > 1] order by rn asc = nvl(sibling_idx[prev_brother_rn[cv()]],-1) + 1,
done[ANY] order by rn asc = nvl(done[cv()-1],0) + case when left[cv()] is not null and right[cv()] is not null then 1 else 0 end,
hasrun[ITERATION_NUMBER+1] = done[lim[1]]
)
) p
;