| René Nyffenegger's collection of things on the web | |
|
René Nyffenegger on Oracle - Most wanted - Feedback
|
September 29, 2009: On summing up values of nodes of a hierarchical query | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Here's a table that stores hierarchical information. Its hierarchical because its column id_p (standing for id of parent) references
the column id of the same table.
create table hierarchical_values ( id number primary key, id_p references hierarchical_values, info varchar2(10), amt number );
This table is filled with some data. Note that the column amt is only filled for leaves.
insert into hierarchical_values values ( 1, null, 'a' , null); insert into hierarchical_values values ( 4, 1, 'ad' , null); insert into hierarchical_values values (10, 4, 'adj' , null); insert into hierarchical_values values (32, 10, 'ADJ1' , 6); insert into hierarchical_values values (33, 10, 'ADJ2' , 7); insert into hierarchical_values values (34, 10, 'ADJ3' , 3); insert into hierarchical_values values (35, 10, 'ADJ4' , 1); insert into hierarchical_values values (11, 4, 'adk' , null); insert into hierarchical_values values (29, 11, 'ADK1' , 3); insert into hierarchical_values values (30, 11, 'ADK2' , 4); insert into hierarchical_values values (31, 11, 'ADK3' , 5); insert into hierarchical_values values (12, 4, 'adl' , null); insert into hierarchical_values values (21, 12, 'adlu' , null); insert into hierarchical_values values (27, 21, 'ADLU1' , 1); insert into hierarchical_values values (28, 21, 'ADLU2' , 2); insert into hierarchical_values values (22, 12, 'adlv' , null); insert into hierarchical_values values (36, 22, 'ADLV1' , 2); insert into hierarchical_values values (37, 22, 'ADLV2' , 7); insert into hierarchical_values values (38, 22, 'ADLV3' , 1); insert into hierarchical_values values (39, 22, 'ADLV4' , 4); insert into hierarchical_values values ( 5, 1, 'ae' , null); insert into hierarchical_values values (13, 5, 'aem' , null); insert into hierarchical_values values (45, 13, 'AEM1' , 22); insert into hierarchical_values values (46, 13, 'AEM2' , 18); insert into hierarchical_values values (47, 13, 'AEM3' , 20); insert into hierarchical_values values (14, 5, 'aen' , null); insert into hierarchical_values values (43, 14, 'AEN1' , 14); insert into hierarchical_values values (44, 14, 'AEN2' , 6); insert into hierarchical_values values (15, 5, 'aeo' , null); insert into hierarchical_values values (48, 15, 'AEO1' , 20); insert into hierarchical_values values ( 6, 1, 'af' , null); insert into hierarchical_values values (49, 6, 'AF1' , 1000); insert into hierarchical_values values ( 2, null, 'b' , null); insert into hierarchical_values values ( 7, 2, 'bg' , null); insert into hierarchical_values values (16, 7, 'bgp' , null); insert into hierarchical_values values (50, 16, 'BGP1' , 25); insert into hierarchical_values values (51, 16, 'BGP2' , 75); insert into hierarchical_values values (17, 7, 'bgq' , null); insert into hierarchical_values values (53, 17, 'BGQ1' , 5); insert into hierarchical_values values (18, 7, 'bgr' , null); insert into hierarchical_values values (52, 18, 'BGR1' , 27); insert into hierarchical_values values ( 8, 2, 'bh' , null); insert into hierarchical_values values ( 3, null, 'c' , null); insert into hierarchical_values values ( 9, 3, 'ci' , null); insert into hierarchical_values values (19, 9, 'cis' , null); insert into hierarchical_values values (23, 19, 'cisw' , null); insert into hierarchical_values values (24, 23, 'ciswx' , null); insert into hierarchical_values values (26, 24, 'ciswxz' , null); insert into hierarchical_values values (40, 26, 'CISWXZ1' , 15); insert into hierarchical_values values (41, 26, 'CISWXZ2' , 16); insert into hierarchical_values values (42, 26, 'CISWXZ3' , 14); insert into hierarchical_values values (25, 23, 'ciswy' , null); insert into hierarchical_values values (55, 25, 'CISWY1' , 30); insert into hierarchical_values values (20, 9, 'cit' , null); insert into hierarchical_values values (54, 20, 'CIT1' , 9);
In order to make it possible to view the hierarchical data in the table more appealing for the human eye,
I create a view:
create view hierarchical_values_v as select rownum rownum_, level level_, id, info, substr(lpad (' ', (level-1)*2) || info,1,30) info_, amt from hierarchical_values start with id_p is null connect by prior id = id_p ;
Selecting from the view gives me:
set pagesize 5000 select substr(info_,1,30), amt from hierarchical_values_v order by rownum_ /*desc*/ ;
SUBSTR(INFO_,1,30) AMT
------------------------------ ----------
a
ad
adj
ADJ1 6
ADJ2 7
ADJ3 3
ADJ4 1
adk
ADK1 3
ADK2 4
ADK3 5
adl
adlu
ADLU1 1
ADLU2 2
adlv
ADLV1 2
ADLV2 7
ADLV3 1
ADLV4 4
ae
aem
AEM1 22
AEM2 18
AEM3 20
aen
AEN1 14
AEN2 6
aeo
AEO1 20
af
AF1 1000
b
bg
bgp
BGP1 25
BGP2 75
bgq
BGQ1 5
bgr
BGR1 27
bh
c
ci
cis
cisw
ciswx
ciswxz
CISWXZ1 15
CISWXZ2 16
CISWXZ3 14
ciswy
CISWY1 30
cit
CIT1 9
Now, I'd like to sum up amt for each node. For instance, the sum of amt for node adlv should
be 14 (Which is the sum of the leaves ADLV1, ADLV2, ADLV3 and ADLV4, or 2+7+1+4 respectively). In the same spirit, the
sum of amt for node cisw should be 75 which is the sum of the nodes ciswx and ciswy which (in
recursive turn) is the some of CISWXZ1, CISWXZ2, CISWXZ3 and CISWY1 or 15+16+14+30.
I can achieve this goal by selecting from the tree if it is turned upside-down (by ordering by rownum_ desc)...
set pagesize 5000 select rownum_, level_, substr(info_,1,30), amt from hierarchical_values_v order by rownum_ desc ;
ROWNUM_ LEVEL_ SUBSTR(INFO_,1,30) AMT
---------- ---------- ------------------------------ ----------
55 4 CIT1 9
54 3 cit
53 6 CISWY1 30
52 5 ciswy
51 7 CISWXZ3 14
50 7 CISWXZ2 16
49 7 CISWXZ1 15
48 6 ciswxz
47 5 ciswx
46 4 cisw
45 3 cis
44 2 ci
43 1 c
... more records snipped ...
The «trick» is to remember the last record's level_ and to compare it with the actual or current record's level_. Depending on whether
the last record's level_ is smaller, equal or greater then the actual level_, three different actions must be performed. In order to keep track
of the last record's level_, the variable
last_level is used. This variable is initialized with 0.
Also, I need to store the sums of amt for each level_ from 1 through the actual record's level_ - 1. A collection seems
to be the appropriate method to do that. So, I create a collection type sum_on_level and a variable (total_sum_on_level) of that type.
I have now everything in order to start iterating over the records:
Read one (1st) record (level_: 4, amt: 9, rownum_: 55)
level_ > last_level: This means, we have to do two «calculations»:
last_level is now 0, nothing can be done for the first step. The second step sets the elements between 0 and 3 (last_level, level_ -1 ) to amt.
The collection total_sum_on_level now looks like
Also, the read record is appended to the result set nodes which now looks like:
Finally,
last_level is set to level_ (which is 4).
Read one (2nd) record (level_: 3, amt: -, rownum_: 54)
level_ < last_level: This means that we only put one record at the end of nodes. The member amt is set to the value of
total_sum_on_level(level_) (which happens to be 9).
So, nodes now looks as:
last_level is set to level_ (which is 3).
Read one (3rd) record (level_: 6, amt: 30, rownum_: 53)
level_ > last_level. First step: add amt to all elements in total_sum_on_level in the range 1 .. last_level-1 (1 .. 2).
Second step: set the elements in total_sum_on_level to amt in the range last_level .. level_ - 1.
Thus,
total_sum_on_level looks now:
The read record is appended to the result set nodes:
Read one (4th) record (level_: 5, amt: -, rownum_: 52)
level_ < last_level: Put the record at the end of nodes with member set to the value of total_sum_on_level(level_):
last_level to 5.
Read one (5th) record (level_: 7, amt: 14, rownum_: 51)
level_ > last_level: add amt to total_sum_on_level(1 .. last_level-1) and set total_sum_on_level(last_level .. level_) to amt:
Append the read record to nodes:
Read one (6th) record (level_: 7, amt: 16, rownum_: 50)
level_ = last_level: This means that we have to cumulate amt to the elements in total_sum_on_level in the range 1 .. level_-1:
Set
last_level to 7.
Read one (7th) record (level_: 7, amt: 15, rownum_: 49)
level_ = last_level: again, we have to cumulate amt to the elements in total_sum_on_level in the range 1 .. level_-1:
Read one (8th) record (level_: 6, amt: -, rownum_: 48)
level_ < last_level: Put the record at the end of nodes with member set to the value of total_sum_on_level(level_):
If this rules are applied to the entire set, and nodes are then displayed in reversed order, we obtain the desired sums for all nodes.
In order to do this, I create a node object type which keeps track of the summed up values of amt for each node and leaf
in the table:
create or replace type node as object ( info varchar2(30), amt number, rownum_ number ); /
Also, I want a collection of nodes which will eventually store the values for each record found in hierarchical_values. So,
I create an approprate type for it:
create or replace type node_t as table of node; /
Lastly, I need a procedure that actually filles the nodes into a variable whose type is node_t. This
procedure will be the member procedure do_sum of the following object:
create or replace type sum_nodes as object ( nodes node_t, constructor function sum_nodes return self as result, member procedure do_sum ); /
And the specification:
create or replace type body sum_nodes as constructor function sum_nodes return self as result is begin nodes := node_t(); return; end sum_nodes; member procedure do_sum is type sum_on_level is table of number index by pls_integer; total_sum_on_level sum_on_level; last_level number := 0; begin for r in ( select level_, rownum_, info_, amt from hierarchical_values_v order by rownum_ desc ) loop nodes.extend; if r.level_ < last_level then nodes(nodes.count) := node(r.info_, total_sum_on_level(r.level_), r.rownum_); elsif r.level_ = last_level then nodes(nodes.count) := node(r.info_, r.amt, r.rownum_); for i in 1 .. r.level_-1 loop total_sum_on_level(i) := nvl(total_sum_on_level(i), 0) + nvl(r.amt, 0); end loop; else -- r.level_ > last_level nodes(nodes.count) := node(r.info_, nvl(r.amt, 0), r.rownum_); for i in 1 .. last_level - 1 loop total_sum_on_level(i) := total_sum_on_level(i) + nvl(r.amt, 0); end loop; for i in last_level .. r.level_ - 1 loop total_sum_on_level(i) := nvl(r.amt, 0); end loop; end if; last_level := r.level_; end loop; end do_sum; end; /
In action...
set serveroutput on size 1000000 format wrapped declare s sum_nodes := sum_nodes(); begin s.do_sum; for r in (select * from table(s.nodes) t order by t.rownum_) loop dbms_output.put_line(rpad(r.info, 30, '.' )|| to_char(r.amt,'9999')); end loop; end; /
a............................. 1146
ad.......................... 46
adj....................... 17
ADJ1.................... 6
ADJ2.................... 7
ADJ3.................... 3
ADJ4.................... 1
adk....................... 12
ADK1.................... 3
ADK2.................... 4
ADK3.................... 5
adl....................... 17
adlu.................... 3
ADLU1................. 1
ADLU2................. 2
adlv.................... 14
ADLV1................. 2
ADLV2................. 7
ADLV3................. 1
ADLV4................. 4
ae.......................... 100
aem....................... 60
AEM1.................... 22
AEM2.................... 18
AEM3.................... 20
aen....................... 20
AEN1.................... 14
AEN2.................... 6
aeo....................... 20
AEO1.................... 20
af.......................... 1000
AF1....................... 1000
b............................. 132
bg.......................... 132
bgp....................... 100
BGP1.................... 25
BGP2.................... 75
bgq....................... 5
BGQ1.................... 5
bgr....................... 27
BGR1.................... 27
bh.......................... 0
c............................. 84
ci.......................... 84
cis....................... 75
cisw.................... 75
ciswx................. 45
ciswxz.............. 45
CISWXZ1........... 15
CISWXZ2........... 16
CISWXZ3........... 14
ciswy................. 30
CISWY1.............. 30
cit....................... 9
CIT1.................... 9
More on OracleThis is an on Oracle article. The most current articles of this series can be found here.
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||