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

Table Functions and Cursor Expressions
Pages: 1, 2, 3, 4, 5

Using a Cursor Expression as an Actual Parameter to a PL/SQL Function

A cursor variable (that is, a variable of type ref cursor) points to an actual cursor, and may be used as a formal parameter to a PL/SQL procedure or function. A cursor expression defines an actual cursor. (Both these statements are true pre-Oracle 9i.) So we'd expect that it would be possible to invoke a PL/SQL procedure or function that has a formal parameter of type ref cursor with a cursor expression as its actual parameter, thus:



My_Function ( cursor ( select my_col from my_tab ) )

In fact, this wasn't allowed under any circumstances pre-Oracle 9i (attempts would result in the ORA-22902 exception). New in Oracle 9i, it is now allowed under certain circumstances--namely, when the function (it can't be a procedure) is invoked in a top- level SQL statement.

Given a function that can be invoked as follows:

declare
  the_cursor  sys_refcursor;
  n number;
begin
  open the_cursor for
    select my_col from my_tab;
  n := My_function (the_cursor);
  close the_cursor;
end;

it can now be called directly within the query, like this:

select 'my_function' my_function 
  from dual
 where 
    my_function ( 
     cursor ( 
        select my_col 
            from my_tab ) ) 
    = 1;

or:

select 'my_function' my_function 
  from dual
 order by 
    my_function ( 
     cursor ( 
        select my_col 
            from my_tab ) );

Most significantly, this syntax is now allowed in the invocation of a table function in the FROM list of a SELECT statement, which we'll explore later in the article.

The "Young Managers" Scenario

Consider the requirement to find those managers in the employees table, the majority of whose direct reports were hired before the manager. The algorithm depends on finding the direct reports for each manager and comparing the number who were hired before him with the number who were hired after him. This can be programmed straightforwardly in PL/SQL using classical techniques. (Note that, seeking to use enhanced Oracle 9i functionality, this is implemented using a single SQL SELECT that has a cursor subquery for the reports of a given manager.) This approach allows the production of a report, or as is illustrated, populating a table with the results.

But suppose the requirement is more subtle: to create a VIEW to represent managers as specified, so that it can be leveraged in ad hoc queries representing the current state of the underlying data. In fact, the requirement in this scenario can be implemented in pure SQL using only SQL functions such as SUM and DECODE. There are some rules that are too complex to implement by DECODE, in which case the user could write his own function.

But this approach, though it works, feels back-to-front! Unlike the classical approach described previously, it doesn't model the simple statement of the algorithm, and is therefore hard to write and to proofread. A more comfortable approach is to define a view as follows:

create view young_managers as
  select ...
    from employees managers
   where most_reports_before_manager( 
     < stuff for this manager > ) = 1;

We can do this classically like so:

create view young_managers as
  select ...
    from employees managers
    where most_reports_before_manager
      (
        managers.employee_id, managers.hire_date
      ) = 1;

or by passing a cursor expression as the actual parameter to a function whose formal parameter is of type REF CURSOR (see Example 3), giving us something more like this:

create view young_managers as
  select ...
    from employees managers
    where most_reports_before_manager
      (
        cursor ( < select statement > ),
        managers.hire_date
      ) = 1;

Example 3. Using a function with a ref cursor parameter in a WHERE clause.

create or replace function Most_Reports_Before_Manager (
  report_hire_dates_cur  in sys_refcursor,
  manager_hire_date      in date ) 
  return number
is
  type report_hire_date_t   is table of employees.hire_date%type
                               index by binary_integer;
  report_hire_dates         report_hire_date_t;
  before integer:=0; after integer:=0;
begin
  fetch report_hire_dates_cur bulk collect into report_hire_dates;
  if report_hire_dates.count > 0
  then
    for j in report_hire_dates.first..report_hire_dates.last
    loop
      case report_hire_dates(j) < manager_hire_date
        when                                   true then before:=before+1;
        else                                             after:=after+1;
      end case;
    end loop;
  end if;
  case before > after
    when         true then return 1;
    else                   return 0;
  end case;
end Most_Reports_Before_Manager;

create or replace view young_managers as
  select managers.employee_id manager_employee_id
    from employees managers
    where Most_Reports_Before_Manager
      (
        cursor ( select reports.hire_date from employees reports
                   where reports.manager_id = managers.employee_id
               ),
        managers.hire_date
      ) = 1;

The Example 3 approach isn't possible before Oracle 9i. Its advantage over the approach of using a classical function in a WHERE clause is marginal rather than dramatic: It offers greater potential for reuse in that its logic is expressed in terms of, and depends only on, the select list for an arbitrary SELECT, whereas the classical approach hard-codes the SELECT. And, since there's only one SQL statement, this can be optimized more effectively than (what the SQL engine sees as) two unconnected SQL statements (as discussed earlier).

The dramatic benefit of the new Oracle 9i feature allowing a cursor expression as an actual parameter to a PL/SQL function comes in connection with table functions, which we'll now explore.

Table Functions: Recap

Suppose we have two schema-level types, a tuple analogous to a table row and a table of these, defined as follows:

create type lookup_row as 
  object ( idx number, text varchar2(20) );
create type lookups_tab as 
  table of lookup_row;

We can then write a PL/SQL function that returns an instance of the table as shown in Example 4. While the example doesn't reflect a "real-world" scenario, it's intended to emphasize the fact that you can model arbitrarily complex logic within the function, something that can be quite the challenge in pure SQL.

Example 4. A function that returns a collection of information.

create or replace function lookups_fn return lookups_tab
is 
   v_table   lookups_tab;
begin
   /*
  to extend a nested table, you must use the built-in 
  procedure extend, but to extend an index-by table, 
  you just specify larger subscripts. 
  */
   v_table := lookups_tab (lookup_row (1, 'one'));

   for j in 2 .. 9
   loop
      v_table.extend;

      if j = 2
      then
         v_table (j) := lookup_row (2, 'two');
      elsif j = 3
      then
         v_table (j) := lookup_row (3, 'three');
      elsif j = 4
      then
         v_table (j) := lookup_row (4, 'four');
      elsif j = 5
      then
         v_table (j) := lookup_row (5, 'five');
      elsif j = 6
      then
         v_table (j) := lookup_row (6, 'six');
      elsif j = 7
      then
         v_table (j) := lookup_row (7, 'seven');
      else
         v_table (j) := lookup_row (j, 'other');
      end if;
   end loop;

   return v_table;
end lookups_fn;

We can then invoke it in the FROM list of a SELECT statement thus:

select * 
  from table ( 
         cast ( lookups_fn() 
           as lookups_tab ) );

This allows a table to be synthesized by computation. For example, the function might call Utl_File procedures (to parse data that can't be handled by the SQL*Loader utility or by the external table feature), or it might call C routines (via the callout framework) that access arbitrary external data sources. Or it might access database tables and perform transformations that can't be expressed with pure SQL and SQL functions. The SELECT statement can be used to define a view, and/or combined with other tables in the FROM list in an arbitrarily complex SQL statement.

A table function, then, is a PL/SQL function that can be invoked in the FROM clause of a SQL SELECT clause. We'll see later that a table function that exploits new Oracle 9i functionality, which we expect all table functions to do, can only be invoked in the FROM clause of a SQL SELECT clause.

Pages: 1, 2, 3, 4, 5

Next Pagearrow




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