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

Pipelined Table Functions: New in Oracle 9i

The preceding functionality is available pre-Oracle 9i. However, it has the limitation that the function must run to completion, storing all the rows it computes in the PL/SQL table before even the first row can be delivered. (There are other limitations, discussed shortly.) Oracle 9i introduces the pipelined construct that allows the procedure to be rewritten as shown in Example 5.



Example 5. A pipelined function.

create or replace function lookups_fn return lookups_tab
  pipelined
is
  v_row lookup_row;
begin
  for j in 1..10
  loop
    v_row := 
      case j
        when 1 then lookup_row ( 1, 'one' )
        ...
        when 7 then lookup_row ( 7, 'seven' )
        else        lookup_row ( j, 'other' )
      end;
    pipe row ( v_row );
  end loop;
  return;
end lookups_fn;

Thus each row is delivered as soon as it's ready, so that the response time characteristics of a table function are symmetrical with those of a row source based on a table scan or an index scan. (For performance, the PL/SQL runtime system delivers the rows from a pipelined table function in batches.)

Note: The procedure body now mentions only rows (that is, not the table), and the table is just implied by the return type. (For elegance, the IF construct has been replaced with the new CASE formulation.) The same syntax as shown earlier can be used to select from the table function, but it can now be simplified thus:

select * from table ( Lookups_Fn );

The invocation will be written Lookups_Fn() in the following to emphasize its status as a function.

Oracle 9i also introduces the possibility to create a table function that returns a PL/SQL type (that is, one that's defined in a PL/SQL block rather than at schema level), as shown in Example 6.

Example 6. A table function that returns a PL/SQL type.

create or replace package My_Types is
  type lookup_row is record ( idx number, text varchar2(20) );
  type lookups_tab is table of lookup_row;
end My_Types;

create or replace function Lookups_Fn return My_Types.lookups_tab
  pipelined
is
  v_row My_Types.lookup_row;
begin
  for j in 1..10
  loop
    case j
      when 1 then v_row.idx := 1; v_row.text := 'one';
      ...
      when 7 then v_row.idx := 7; v_row.text := 'seven';
      else        v_row.idx := j; v_row.text := 'other';
    end case;
    pipe row ( v_row );
  end loop;
  return;
end Lookups_Fn;

In the limit, a PL/SQL type may be defined in the declare section of an anonymous block and hence have no persistence. However, to be useful in connection with table functions, the PL/SQL types must be declared in a package, and so when discussing table functions, they're usually referred to as package-level types (in contrast to schema-level types).

Note: A table function that returns a package-level type must be pipelined. Moreover, the simpler SELECT syntax (without the CAST) must be used.

Piping Data from One Table Function to the Next

Also new to Oracle 9i, a table function may now be defined with an input parameter of type ref cursor and invoked with a cursor expression as the actual parameter. Consider the code shown in Example 7.

Example 7. A transformative pipelined function.

create or replace function Mappings_Fn ( p_input_rows in sys_refcursor )
  return My_Types.lookups_tab
  pipelined
is
  v_in_row  My_Types.lookup_row;
  v_out_row My_Types.lookup_row;
begin
/*
  The following causes...
  PLS-00361: IN cursor 'P_INPUT_ROWS' cannot be OPEN'ed
  (The system opens the cursor on invoking the function.)
*/
--open p_input_rows;
  loop
    fetch p_input_rows into v_in_row;
    exit when p_input_rows%notfound;

    case v_in_row.idx
      when 1 then v_out_row.idx :=  1*2; v_out_row.text := 'was one';
      when 2 then v_out_row.idx :=  2*3; v_out_row.text := 'was TWO';
      when 3 then v_out_row.idx :=  3*4; v_out_row.text := 'was three';
      when 4 then v_out_row.idx :=  4*5; v_out_row.text := 'was FOUR';
      when 5 then v_out_row.idx :=  5*6; v_out_row.text := 'was five';
      when 6 then v_out_row.idx :=  6*7; v_out_row.text := 'was SIX';
      when 7 then v_out_row.idx :=  7*8; v_out_row.text := 'was seven';
      else        v_out_row.idx :=
                        v_in_row.idx*10; v_out_row.text := 'was other';
    end case;
    pipe row ( v_out_row );
  end loop;
  close p_input_rows;
  return;
end Mappings_Fn;

Suppose t is a table that supports a select list compatible with My_Types.lookup_row. We can now invoke the table function thus:

select * 
  from table ( 
     mappings_fn ( 
        cursor ( 
         select idx, text 
         from t ) ) );

Of course, t might have been a view defined thus:

create or replace view t 
as
  select * from table ( lookups_fn() );

which implies the more compact syntax shown in Example 8.

Example 8. Passing a query as an argument.

create or replace view v as
  select *
    from table ( Mappings_Fn ( cursor ( select * from table ( Lookups_Fn() ) ) ) );

Data can be piped from one to the next of an arbitrary number of table functions daisy- chained in succession. And, due to the pipelining feature, storage of intermediate results is avoided. Table functions can thus be used to implement the Extraction, Transformation, and Load (a.k.a. ETL) operation for building a data warehouse from OLTP data. In the limit, the extraction table function would access a foreign data source as discussed earlier.

The "Young Managers" Scenario Revisited: Table Function Approach

We can now use yet another approach! The complete solution can be implemented in a table function. This has the usability advantage of keeping all the logic in one place, and the performance advantage of invoking the function only once rather than once per row in the table (see Example 9). This was derived "mechanically" simply by creating an appropriate PL/SQL table type and by creating the block as a pipelined function to return that type, substituting pipe row ( manager_employee_id ) for insert into young_managers values ( manager_employee_id ).

Example 9. Using a table function.

create or replace package My_Types is
  type employee_ids_tab is table of employees.employee_id%type;
end My_Types;

create or replace function Young_Managers_Fn
  return My_Types.employee_ids_tab
  pipelined
is
  cursor managers is
    select
      employee_id, hire_date,
      cursor (
              select hire_date
                from employees reports
                where reports.manager_id = managers.employee_id
             )
      from employees managers;

  manager_employee_id       employees.employee_id%type;
  manager_hire_date         employees.hire_date%type;
  reports                   sys_refcursor;
  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; after integer;
begin
  open managers;
  loop
    before:=0; after:=0;
    fetch managers into manager_employee_id, manager_hire_date, reports;
    exit when managers%notfound;
    fetch reports 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;
    if before > after then
      pipe row ( manager_employee_id ); end if;
  end loop;
  close managers;
  return;
end Young_Managers_Fn;

create or replace view young_managers as
  select column_value manager_employee_id from table ( Young_Managers_Fn() );

The function can be made more general by giving it a ref cursor input parameter and by passing in the cursor expression as the actual parameter (see Example 10). This would allow it to be "pointed at" any table that expressed a hierarchy where both parent and child have a date.

Example 10. Using a table function with a ref cursor Input parameter.

create or replace function Young_Managers_Fn ( managers in sys_refcursor )
  return My_Types.employee_ids_tab
  pipelined
is
  manager_employee_id       employees.employee_id%type;
  manager_hire_date         employees.hire_date%type;
  reports                   sys_refcursor;
  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; after integer;
begin
  loop
    before:=0; after:=0;
    fetch managers into manager_employee_id, manager_hire_date, reports;
    exit when managers%notfound;
    fetch reports 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;
    if before > after then
      pipe row ( manager_employee_id ); end if;
  end loop;
  close managers;
  return;
end Young_Managers_Fn;

select column_value manager_employee_id from table
 (
   Young_Managers_Fn
     (
       cursor
         (
            select
              employee_id, hire_date,
              cursor (
                        select hire_date
                          from employees reports
                          where reports.manager_id = 
                            managers.employee_id
                     )
              from employees managers
         )
     )
 );

Note: An attempt to create a view as this SELECT statement currently fails with "ORA-22902: CURSOR expression not allowed," where the exception is raised because the SELECT statement that's the argument of the CURSOR formal parameter to the table function itself has a cursor expression (a.k.a. cursor subquery). A view can be created when the SELECT statement doesn't have a cursor subquery (see the earlier Mappings_Fn example).

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