Catching NO_DATA_NEEDED Exception in Pipelined Functions

Pipelined Functions are functions whose output is referenced as a table in a SQL statement. Here's an example of a simple pipelined function:

-- Prefix: Create supporting objects
create or replace type emp_info as object (
  ename varchar2(255),
  job varchar2(255)
);
/

create or replace type emp_info_arr as table of emp_info;
/

-- Pipelined Function (with error handling)
create or replace function f_pipeline_demo
  return emp_info_arr pipelined
as
  l_scope logger_logs.scope%type := lower($$plsql_unit);
begin
  for x in (
    select ename, job
    from emp
  ) loop
    pipe row (emp_info(x.ename, x.job));
  end loop;

exception
  when others then
    logger.log_error('Unhandled Exception', l_scope); 
end;
/


-- Query First 5 rows of Pipelined Function (they're 14 ros in emp table)
select *
from table(f_pipeline_demo)
where rownum <= 5
;

   ENAME          JOB
________ ____________
KING     PRESIDENT
BLAKE    MANAGER
CLARK    MANAGER
JONES    MANAGER
SCOTT    ANALYST

The above demo serves two purposes. First to provide a basic example of a pipelined function and second to show a "hidden error" in its implementation. No errors were raised when querying the pipelined function, however looking at the logger output shows that an error was raised:

select logger_level, text
from logger_logs_5_min
where 1=1
  and scope like 'f_pipeline_demo'
;

 LOGGER_LEVEL  TEXT  
 ____________  ____
            2  Unhandled Exception ORA-06548: no more rows needed ...

Taking a step back, the emp table has 14 rows in it. Querying f_pipeline_demo was limited to the first 5 rows by adding where rownum <= 5. When a pipelined function is called and it realizes that it no longer needs to process additional rows it stops by raising the no_data_needed.

At first glance this may seem like a weird/bad outcome but it makes sense. Pipelined functions sometimes contain some costly computations in each loop. If only 5 rows are returned, doing the additional computations isn't required.

Since I log all exceptions in my code, I don't want to log no_data_needed exceptions as it's not a "bad" exception. To get around this I can simply change the exception block in the pipelined function to:


...
exception
  -- Make sure this is before the "when others then" block
  when no_data_needed then
    null;
  when others then
    logger.log_error('Unhandled Exception', l_scope); 
end;
/

Now when the pipelined function is queried with a restricted set of rows the no_data_needed exception won't be logged.