Multi-Level Collections in Oracle 9i
Pages: 1, 2, 3, 4
Writing a Table Function to Output a Nested "View" from the Relational Representation
Suppose it had been decided to implement the persistent storage of the collection as a relational representation. It's still possible to view it as if it were the nested table representation by using a table function. The simplest design would use nested PL/SQL cursor loops, thus:
for each distinct runner… ;
for each distinct week for that runner… ;
for each run for that week for that runner
add the object to represent the run
to the column in the collection for that week;
when done with that week add the column
for the whole week to the "plane"
of the collection for that runner's log;
when done with that runner,
pipe the record representing the name
and the training log collection.
To make the table function more general, it needs to have a ref cursor input
parameter to be invoked with a SELECT having two levels of nested CURSOR
subqueries corresponding to the preceding nested PL/SQL loops. An alternative is
to design the function to accept a "flat" SELECT. The latter approach requires
slightly more elaborate coding of the function logic (to explicitly detect the
next week and the next runner) but makes the resulting function substantially
more user-friendly, and so it was selected for implementation in this
illustration. To make the example richer with respect to understanding table
functions, parallelization declarations are added to ensure that all the rows
for a particular runner go consecutively to the same slave, and that for that
runner the input rows are ordered by week and then run. (The algorithm depends
on these assumptions.)
This logic is shown in Example 6.
Example 6. Table function to "view" the contents of the relational table as a nested table.
create or replace function nested_training_logs_fn
( p_reln_training_logs my_types.cur_t )
return my_types.nested_training_logs_tab_t
/*
The algorithm depends on assuming that it receives
rows ordered by first_name, week, then run, and that
all the rows for a particular runner go consecutively
to the same slave. These declarations ensure this and
remove the need for an ORDER BY clause in the SELECT
that's used to invoke this function.
*/
order p_reln_training_logs by ( first_name, week, run )
parallel_enable (
partition p_reln_training_logs by range ( first_name ) )
pipelined
is
g_in_row my_types.reln_training_log_row_t;
g_out_row my_types.nested_training_log_row_t;
g_weeks_running weeks_running_t;
g_training_log training_log_t;
g_first_time boolean := true;
g_got_a_row boolean;
g_new_week boolean;
g_new_runner boolean;
g_current_first_name reln_training_logs.first_name%type;
g_prev_first_name reln_training_logs.first_name%type;
g_current_week reln_training_logs.week%type;
g_prev_week reln_training_logs.week%type;
procedure get_next_row is begin
fetch p_reln_training_logs into g_in_row;
g_got_a_row := not p_reln_training_logs%notfound;
if g_got_a_row
then
case g_first_time
when true then
g_first_time := false;
g_new_runner := false;
g_new_week := false;
else
g_new_runner :=
g_prev_first_name <> g_in_row.first_name;
g_new_week := case g_new_runner
when true then true
else
g_prev_week <> g_in_row.week
end;
end case;
g_prev_first_name := g_in_row.first_name;
g_prev_week := g_in_row.week;
end if;
return;
end get_next_row;
function got_next_runner return boolean is begin
g_current_first_name := g_in_row.first_name;
g_new_runner := false;
return g_got_a_row;
end got_next_runner;
function got_next_week return boolean is begin
g_current_week := g_in_row.week;
g_new_week := false;
return ( not g_new_runner ) and g_got_a_row;
end got_next_week;
function got_next_run return boolean is begin
return ( not g_new_week ) and g_got_a_row;
end got_next_run;
procedure new_training_log is begin
g_training_log := null;
end new_training_log;
procedure new_weeks_running is begin
g_weeks_running := null;
end new_weeks_running;
procedure store_this_run is begin
if g_weeks_running is null
then
g_weeks_running :=
weeks_running_t ( run_t ( 0, 0 ) );
else
g_weeks_running.extend;
end if;
g_weeks_running ( g_in_row.run ):=
run_t ( g_in_row.distance, g_in_row.pace );
end store_this_run;
procedure store_this_weeks_running is begin
if g_training_log is null
then
g_training_log :=
training_log_t (
weeks_running_t ( run_t ( 0, 0 ) ) );
else
g_training_log.extend;
end if;
g_training_log ( g_current_week ):= g_weeks_running;
end store_this_weeks_running;
procedure output_this_runner is begin
g_out_row.first_name := g_current_first_name;
g_out_row.training_log := g_training_log;
end output_this_runner;
begin
get_next_row();
while got_next_runner()
loop
new_training_log;
while got_next_week()
loop
new_weeks_running;
while got_next_run()
loop
store_this_run;
get_next_row();
end loop;
store_this_weeks_running;
end loop;
output_this_runner; pipe row ( g_out_row );
end loop;
close p_reln_training_logs;
return;
end nested_training_logs_fn;
In Summary
You'll find in Example 7 an "end-to-end" test that populates two relational tables used in the relational and nested representations and then validates that in fact they're identical in content. This test demonstrates the range of options developers now have to find solutions to their problems.
Multi-level collections certainly introduce an additional level of complexity for PL/SQL developers. They also, however, widen the range of real-world scenarios that can be modeled accurately and efficiently within PL/SQL programs.
Example 7. An "end-to-end" test.
truncate table nested_training_logs;
execute populate_nested_training_logs
truncate table reln_training_logs;
insert into reln_training_logs
(
select *
from table
(
reln_training_logs_fn
(
cursor
(
select first_name, training_log
from nested_training_logs
)
)
)
);
truncate table nested_training_logs_2;
insert into nested_training_logs_2
(
select *
from table
(
nested_training_logs_fn
(
cursor ( select * from reln_training_logs )
)
)
);
truncate table reln_training_logs_2;
insert into reln_training_logs_2
(
select *
from table
(
reln_training_logs_fn
(
cursor
(
select first_name, training_log
from nested_training_logs_2
)
)
)
);
select *
from reln_training_logs_2
minus
select *
from reln_training_logs;
select *
from reln_training_logs
minus
select *
from reln_training_logs_2;
This article was originally published in the June 2002 issue of Oracle Professional. The material in Feuerstein's articles--and those he cowrote with Bryn Llewellyn--are based on Oracle Corporation white papers originally prepared by Llewellyn for Oracle OpenWorld 2001 in San Francisco and OracleWorld Copenhagen in June 2002, and Feuerstein's book, Oracle PL/SQL Programming, 3rd Edition.
Steven Feuerstein is considered one of the world's leading experts on the Oracle PL/SQL language, having written ten books on the subject. Steven is a Senior Technology Advisor with Quest Software and has been developing software since 1980.
Bryn Llewellyn is PL/SQL Product Manager, Database and Application Server Technologies Development Group, at Oracle Corporation Headquarters.
O'Reilly & Associates released Oracle PL/SQL Programming, 3rd Edition in September 2002.
-
Sample Chapter 10, Dates and Timestamps, is available free online.
-
You can also look at the Table of Contents, the Index, and the Full Description of the book.
-
For more information, or to order the book, click here.
Return to the O'Reilly Network.



