Adding a column to the output of a polymorphic table function is a two step process.
- in describe() define the new column
in fetch_rows() populate the new column, this can be performed via either:
- dbms_tf.put_col()
- dbms_tf.put_row_set()
(Note: This is the first lesson in the tutorial to use a fetch_rows() procedure.)
For step 1 we need to learn and use the dbms_tf.describe_t data type. We can visualize dbms_tf.describe_t like this.
describe_t.new_columns
describe_t.new_columns(i)
describe_t.new_columns(i).name
describe_t.new_columns(i).name_len
describe_t.new_columns(i).type
describe_t.new_columns(i).max_len
describe_t.new_columns(i).precision
describe_t.new_columns(i).scale
describe_t.new_columns(i).charsetid
describe_t.new_columns(i).charsetform
describe_t.new_columns(i).collation
describe_t.cstore_chr
describe_t.cstore_chr(a)
describe_t.cstore_num
describe_t.cstore_num(a)
describe_t.cstore_bol
describe_t.cstore_bol(a)
describe_t.cstore_dat
describe_t.cstore_dat(a)
describe_t.method_names
describe_t.method_names(a)
For step 2 we need to learn and use the tab_varchar2_t data type (or tab_number_t, tab_date_t, etc.) to populate our new columns via put_col().
TYPE TAB_VARCHAR2_T IS TABLE OF VARCHAR2(32767) INDEX BY PLS_INTEGER;
Alternately we can use the dbms_tf.row_set_t data type to populate the new column via put_row_set(). We can visualize dbms_tf.row_set_t like this.
row_set_t
row_set_t(i).description
row_set_t(i).description.name
row_set_t(i).description.name_len
row_set_t(i).description.type
row_set_t(i).description.max_len
row_set_t(i).description.precision
row_set_t(i).description.scale
row_set_t(i).description.charsetid
row_set_t(i).description.charsetform
row_set_t(i).description.collation
row_set_t(i).tab_varchar2(j)
row_set_t(i).tab_number(j)
row_set_t(i).tab_date(j)
row_set_t(i).tab_binary_float(j)
row_set_t(i).tab_binary_double(j)
row_set_t(i).tab_raw(j)
row_set_t(i).tab_char(j)
row_set_t(i).tab_clob(j)
row_set_t(i).tab_blob(j)
row_set_t(i).tab_timestamp(j)
row_set_t(i).tab_timestamp_tz(j)
row_set_t(i).tab_interval_ym(j)
row_set_t(i).tab_interval_ds(j)
row_set_t(i).tab_timestamp_ltz(j)
row_set_t(i).tab_rowid(j)
Note that the "description" record contains a description of any FOR_READ=TRUE columns retrieved via get_row_set(). fetch_rows() should only read this information, not write to it. Writing to it has no effect on the end result.
To better understand how row_set_t works take a look at what a DEPT row set might look like.
row_set_t( 1 ).description.name = 'DEPTNO' ; row_set_t( 1 ).description.type = dbms_tf.type_number ; row_set_t( 1 ).description.precision = 2 ; row_set_t( 1 ).description.scale = 0 ; row_set_t( 1 ).tab_number = ( 10, 20, 30, 40 ) ; row_set_t( 2 ).description.name = 'DNAME' ; row_set_t( 2 ).description.type = dbms_tf.type_varchar2 ; row_set_t( 2 ).description.max_len = 14 ; row_set_t( 2 ).tab_varchar2 = ( 'ACCOUNTING', 'RESEARCH', 'SALES', 'OPERATIONS' ) row_set_t( 3 ).description.name = 'LOC' ; row_set_t( 3 ).description.type = dbms_tf.type_varchar2 ; row_set_t( 3 ).description.max_len = 13 ; row_set_t( 3 ).tab_varchar2 = ( 'NEW YORK', 'DALLAS', 'CHICAGO', 'BOSTON' )
PUT_COL()
Let's look at an example using the PUT_COL() method.
create or replace package ptf_demo as
function describe( p_input_table_metadata in out dbms_tf.table_t ) return dbms_tf.describe_t ;
procedure fetch_rows ;
function augment ( p_table table)
return table pipelined row polymorphic using ptf_demo ;
end ptf_demo ;
/
create or replace package body ptf_demo as
function describe( p_input_table_metadata in out dbms_tf.table_t )
return dbms_tf.describe_t
as
v_new_column_desc dbms_tf.describe_t ;
begin
--
-- step (1) define the new column
--
v_new_column_desc.new_columns(1).name := 'THE_NEW_COLUMN' ;
v_new_column_desc.new_columns(1).type := dbms_tf.type_varchar2 ;
v_new_column_desc.new_columns(1).max_len := 100 ;
return v_new_column_desc ;
end describe ;
procedure fetch_rows as
v_new_column dbms_tf.tab_varchar2_t ;
begin
--
-- step (2) populate the new column
--
v_new_column(1) := 'hello' ;
v_new_column(2) := 'world' ;
dbms_tf.put_col( 1, v_new_column );
end fetch_rows ;
end ptf_demo ;
/
select *
from ptf_demo.augment( scott.dept ) ;
DEPTNO DNAME LOC THE_NEW_COLUMN
---------- ---------- ---------- ----------------------------------------------------------------------------------------------------
10 ACCOUNTING NEW YORK hello
20 RESEARCH DALLAS world
30 SALES CHICAGO
40 OPERATIONS BOSTON
PUT_ROW_SET()
Now let's look at a similar example which uses the PUT_ROW_SET() method. We only need to change the package body for this example. The package specification remains the same as above.
create or replace package body ptf_demo as
function describe( p_input_table_metadata in out dbms_tf.table_t )
return dbms_tf.describe_t
as
v_new_column_desc dbms_tf.describe_t ;
begin
--
-- step (1) define the new column
--
v_new_column_desc.new_columns(1).name := 'THE_NEW_COLUMN' ;
v_new_column_desc.new_columns(1).type := dbms_tf.type_varchar2 ;
v_new_column_desc.new_columns(1).max_len := 100 ;
return v_new_column_desc ;
end describe ;
procedure fetch_rows as
v_new_columns dbms_tf.row_set_t;
begin
--
-- step (2) populate the new column
--
v_new_columns(1).tab_varchar2(1) := 'hello' ;
v_new_columns(1).tab_varchar2(2) := 'world' ;
dbms_tf.put_row_set( v_new_columns );
end fetch_rows ;
end ptf_demo ;
/
select *
from ptf_demo.augment( scott.dept );
DEPTNO DNAME LOC THE_NEW_COLUMN
---------- ---------- ---------- ----------------------------------------------------------------------------------------------------
10 ACCOUNTING NEW YORK hello
20 RESEARCH DALLAS world
30 SALES CHICAGO
40 OPERATIONS BOSTON
DBMS_TF.ROW_SET_T.DESCRIPTION
Above we claimed that updating the description record has no effect on the output. Let's confirm this with a test.
create or replace package body ptf_demo as
function describe( p_input_table_metadata in out dbms_tf.table_t )
return dbms_tf.describe_t
as
v_new_column_desc dbms_tf.describe_t ;
begin
v_new_column_desc.new_columns(1).name := 'THE_NEW_COLUMN' ;
v_new_column_desc.new_columns(1).type := dbms_tf.type_varchar2 ;
v_new_column_desc.new_columns(1).max_len := 100 ;
return v_new_column_desc ;
end describe ;
procedure fetch_rows as
v_new_columns dbms_tf.row_set_t;
begin
v_new_columns(1).description.name := 'A_DIFFERENT_COLUMN_NAME' ;
dbms_tf.put_row_set( v_new_columns );
end fetch_rows ;
end ptf_demo ;
/
select *
from ptf_demo.augment( scott.dept ) ;
DEPTNO DNAME LOC THE_NEW_COLUMN
---------- ---------- ---------- ----------------------------------------------------------------------------------------------------
10 ACCOUNTING NEW YORK
20 RESEARCH DALLAS
30 SALES CHICAGO
40 OPERATIONS BOSTON
Note how the column name remained set to the value defined in describe(), i.e. "THE_NEW_COLUMN". The column name set in fetch_rows(), "A_DIFFERENT_COLUMN_NAME", was ignored.
NO_DATA_FOUND Exceptions
Be sure to avoid or properly handle NO_DATA_FOUND exceptions in fetch_rows(). When executing a PL/SQL procedure or function, e.g. fetch_rows(), the SQL engine does not treat NO_DATA_FOUND exceptions as errors. Instead it simply stops processing when it encounters NO_DATA_FOUND, see AskTOM "NO_DATA_FOUND in Functions").
This is what happens when fetch_rows() raises NO_DATA_FOUND.
create or replace package body ptf_demo as
function describe( p_input_table_metadata in out dbms_tf.table_t )
return dbms_tf.describe_t
as
v_new_column_desc dbms_tf.describe_t ;
begin
v_new_column_desc.new_columns(1).name := 'THE_NEW_COLUMN' ;
v_new_column_desc.new_columns(1).type := dbms_tf.type_varchar2 ;
v_new_column_desc.new_columns(1).max_len := 100 ;
return v_new_column_desc ;
end describe ;
procedure fetch_rows as
v_new_columns dbms_tf.row_set_t;
v_some_string varchar2(100);
begin
v_new_columns( 1 ).tab_varchar2(1) := 'hello world' ;
--
-- let's accidentally raise a NO_DATA_FOUND exception by referencing
-- a non-existent array index
--
v_some_string := v_new_columns( 99 ).tab_varchar2(1) ;
dbms_tf.put_row_set( v_new_columns );
end fetch_rows ;
end ptf_demo ;
/
select *
from ptf_demo.augment( scott.dept ) ;
DEPTNO DNAME LOC THE_NEW_COLUMN
---------- ---------- ---------- ----------------------------------------------------------------------------------------------------
10 ACCOUNTING NEW YORK
20 RESEARCH DALLAS
30 SALES CHICAGO
40 OPERATIONS BOSTON
Note how no error was raised. Note also that put_row_set() failed to execute, i.e. "hello world" did not appear in the output.
Referencing Input Table Data
To reference input table data when setting a new output column value we must do two things.
- in describe() identify the input columns we wish to reference by setting their FOR_READ attribute to TRUE
in fetch_rows() retrieve the FOR_READ=TRUE columns using either:
- dbms_tf.get_col()
- dbms_tf.get_row_set()
Let's see an example using the get_col() method.
create or replace package body ptf_demo as
function describe( p_input_table_metadata in out dbms_tf.table_t )
return dbms_tf.describe_t
as
v_new_column_desc dbms_tf.describe_t ;
begin
--
-- step (1) identify the input columns we wish to reference by setting FOR_READ to TRUE
-- (column #3 in DEPT is the LOC column)
--
p_input_table_metadata.column(3).for_read := true ;
--
-- define new columns
--
v_new_column_desc.new_columns(1).name := 'THE_NEW_COLUMN' ;
v_new_column_desc.new_columns(1).type := dbms_tf.type_varchar2 ;
v_new_column_desc.new_columns(1).max_len := 100 ;
return v_new_column_desc ;
end describe ;
procedure fetch_rows as
v_loc dbms_tf.tab_varchar2_t ;
v_new_column dbms_tf.tab_varchar2_t ;
i pls_integer ;
begin
--
-- step (2) retrieve the FOR_READ=TRUE columns
--
dbms_tf.get_col( 1, v_loc );
--
-- loop through the for_read rows and set new column values
--
i := v_loc.first ;
while i is not null loop
v_new_column(i) := lower( v_loc(i) ) ;
i := v_loc.next(i) ;
end loop ;
--
-- output our new values
--
dbms_tf.put_col( 1, v_new_column );
end fetch_rows;
end;
/
select *
from ptf_demo.augment( scott.dept ) ;
DEPTNO DNAME LOC THE_NEW_COLUMN
---------- ---------- ---------- ----------------------------------------------------------------------------------------------------
10 ACCOUNTING NEW YORK new york
20 RESEARCH DALLAS dallas
30 SALES CHICAGO chicago
40 OPERATIONS BOSTON boston
Here is an exmaple using the get_row_set() method.
create or replace package body ptf_demo as
function describe( p_input_table_metadata in out dbms_tf.table_t )
return dbms_tf.describe_t
as
v_new_column_desc dbms_tf.describe_t ;
begin
--
-- step (1) identify the input columns we wish to reference by setting FOR_READ to TRUE
--
p_input_table_metadata.column(2).for_read := true ; -- (1) DNAME
p_input_table_metadata.column(3).for_read := true ; -- (2) LOC
--
-- define new columns
--
v_new_column_desc.new_columns(1).name := 'THE_NEW_COLUMN' ;
v_new_column_desc.new_columns(1).type := dbms_tf.type_varchar2 ;
v_new_column_desc.new_columns(1).max_len := 100 ;
return v_new_column_desc ;
end describe ;
procedure fetch_rows as
v_for_read_columns dbms_tf.row_set_t;
v_new_columns dbms_tf.row_set_t;
i pls_integer ;
begin
--
-- step (2) retrieve the FOR_READ=TRUE columns
--
dbms_tf.get_row_set( rowset => v_for_read_columns );
--
-- loop through the for_read rows and set new column values
--
i := v_for_read_columns(1).tab_varchar2.first ;
while i is not null loop
v_new_columns(1).tab_varchar2(i) :=
v_for_read_columns(1).tab_varchar2(i) || ' (' ||
v_for_read_columns(2).tab_varchar2(i) || ')' ;
i := v_for_read_columns(1).tab_varchar2.next(i) ;
end loop ;
--
-- output our new values
--
dbms_tf.put_row_set( v_new_columns );
end fetch_rows;
end;
/
select *
from ptf_demo.augment( scott.dept ) ;
DEPTNO DNAME LOC THE_NEW_COLUMN
---------- ---------- ---------- ----------------------------------------------------------------------------------------------------
10 ACCOUNTING NEW YORK ACCOUNTING (NEW YORK)
20 RESEARCH DALLAS RESEARCH (DALLAS)
30 SALES CHICAGO SALES (CHICAGO)
40 OPERATIONS BOSTON OPERATIONS (BOSTON)
