O'Reilly Databases

oreilly.comSafari Books Online.Conferences.

We've expanded our coverage and improved our search! Search for all things Database across O'Reilly!

Search Search Tips

advertisement
AddThis Social Bookmark Button

Listen Print Subscribe to Databases Subscribe to Newsletters

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.




Tagged Articles

Be the first to post this article to del.icio.us

Sponsored Resources

  • Inside Lightroom

Related to this Article

Understanding Oracle Clinical Understanding Oracle Clinical
by Joan M. Johnson
June 2009
$9.99 USD

New Features in Oracle 9i New Features in Oracle 9i
by Howard J. Rogers
June 2009
$5.95 USD

Advertisement
O'Reilly Media

©2009, O'Reilly Media, Inc.
(707) 827-7000 / (800) 998-9938
All trademarks and registered trademarks appearing on oreilly.com are the property of their respective owners.
About O'Reilly
Academic Solutions
Authors
Contacts
Customer Service
Jobs
Newsletters
O'Reilly Labs
Press Room
Privacy Policy
RSS Feeds
Terms of Service
User Groups
Writing for O'Reilly
Content Archive
Business Technology
Computer Technology
Google
Microsoft
Mobile
Network
Operating System
Digital Photography
Programming
Software
Web
Web Design
More O'Reilly Sites
O'Reilly Radar
Ignite
Tools of Change for Publishing
Digital Media
Inside iPhone
makezine.com
craftzine.com
hackszine.com
perl.com
xml.com

Partner Sites
InsideRIA
java.net
O'Reilly Insights on Forbes.com