Ploymorphic table functions have the ability to return N rows for every input row. N is called the "replication factor". It can be 0, 1, or any other positive integer.
Setting the replication factor is a two step process.
- in describe() set ROW_REPLICATION = TRUE
in fetch_rows() do one of the following:
- call DBMS_TF.ROW_REPLICATION()
- call PUT_ROW_SET( REPLICATION_FACTOR => ... )
ROW_REPLICATION()
Let's look at an exmaple using the ROW_REPLICATION() method.
create or replace package ptf_demo as
function describe(
p_input_table_metadata in out dbms_tf.table_t,
p_replication_factor in pls_integer )
return
dbms_tf.describe_t ;
procedure fetch_rows( p_replication_factor pls_integer ) ;
function replicate_rows( p_table table, p_replication_factor pls_integer )
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,
p_replication_factor in pls_integer )
return
dbms_tf.describe_t
as
begin
--
-- step (1) set ROW_REPLICATION = TRUE
--
return dbms_tf.describe_t( row_replication => true ) ;
end describe ;
procedure fetch_rows( p_replication_factor pls_integer ) as
begin
--
-- step (2) set the REPLICATION FACTOR
--
DBMS_TF.ROW_REPLICATION( P_REPLICATION_FACTOR );
end fetch_rows ;
end;
/
Let's test the PTF using various replication factors.
select *
from
ptf_demo.replicate_rows(
p_table => scott.dept,
p_replication_factor => 0 ) ;
no rows selected
select *
from
ptf_demo.replicate_rows(
p_table => scott.dept,
p_replication_factor => 1 ) ;
DEPTNO DNAME LOC
---------- ---------- ----------
10 ACCOUNTING NEW YORK
20 RESEARCH DALLAS
30 SALES CHICAGO
40 OPERATIONS BOSTON
select *
from
ptf_demo.replicate_rows(
p_table => scott.dept,
p_replication_factor => 2 ) ;
DEPTNO DNAME LOC
---------- ---------- ----------
10 ACCOUNTING NEW YORK
10 ACCOUNTING NEW YORK
20 RESEARCH DALLAS
20 RESEARCH DALLAS
30 SALES CHICAGO
30 SALES CHICAGO
40 OPERATIONS BOSTON
40 OPERATIONS BOSTON
PUT_ROW_SET( REPLICATION_FACTOR => ... )
Now let's look at an exmple using the PUT_ROW_SET( REPLICATION_FACTOR => ... ) 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,
p_replication_factor in pls_integer )
return
dbms_tf.describe_t
as
begin
--
-- step (1) set ROW_REPLICATION = TRUE
--
return dbms_tf.describe_t( ROW_REPLICATION => TRUE ) ;
end describe ;
procedure fetch_rows( p_replication_factor pls_integer ) as
v_input_row_set dbms_tf.row_set_t ;
begin
dbms_tf.get_row_set( rowset => v_input_row_set ) ;
--
-- step (2) set the REPLICATION_FACTOR
--
dbms_tf.put_row_set( rowset => v_input_row_set, REPLICATION_FACTOR => P_REPLICATION_FACTOR );
end fetch_rows ;
end;
/
select *
from
ptf_demo.replicate_rows(
p_table => scott.dept,
p_replication_factor => 3 ) ;
DEPTNO DNAME LOC
---------- ---------- ----------
10 ACCOUNTING NEW YORK
10 ACCOUNTING NEW YORK
10 ACCOUNTING NEW YORK
20 RESEARCH DALLAS
20 RESEARCH DALLAS
20 RESEARCH DALLAS
30 SALES CHICAGO
30 SALES CHICAGO
30 SALES CHICAGO
40 OPERATIONS BOSTON
40 OPERATIONS BOSTON
40 OPERATIONS BOSTON
Matching the number of output rows to the number of input rows
We need to ensure the number of rows in our output row set accounts for the replication factor. Here's what happens when we don't. (The following code is a deliberate example of a bug. The output will not appear the way we actually want it to.)
create or replace package body ptf_demo as
function describe(
p_input_table_metadata in out dbms_tf.table_t,
p_replication_factor in pls_integer )
return
dbms_tf.describe_t
as
v_new_column_desc dbms_tf.describe_t ;
begin
p_input_table_metadata.column(1).for_read := true ;
--
-- define a new column
--
v_new_column_desc.new_columns(1).name := 'INTROSPECTION' ;
v_new_column_desc.new_columns(1).type := dbms_tf.type_varchar2 ;
v_new_column_desc.new_columns(1).max_len := 100 ;
-- enable row replication
v_new_column_desc.row_replication := true ;
return v_new_column_desc ;
end describe ;
procedure fetch_rows( p_replication_factor pls_integer ) as
v_for_read_column dbms_tf.tab_number_t ;
v_new_column dbms_tf.tab_varchar2_t ;
i pls_integer ;
begin
dbms_tf.get_col( 1, v_for_read_column );
--
-- loop through the for_read rows and set new column values
--
i := v_for_read_column.first ;
while i is not null loop
-- this next command demonstrates the wrong way to generate output
-- rows when replication is used
v_new_column(i) :=
'for deptno #' || v_for_read_column(i) ||
', input row #' || i ||
', output row #' || i ;
i := v_for_read_column.next(i) ;
end loop ;
dbms_tf.row_replication( p_replication_factor );
dbms_tf.put_col( 1, v_new_column );
end fetch_rows;
end;
/
select
deptno, dname, loc, introspection
from
ptf_demo.replicate_rows(
p_table => scott.dept,
p_replication_factor => 2 ) ;
DEPTNO DNAME LOC INTROSPECTION
---------- ---------- ---------- ------------------------------------------------------------
10 ACCOUNTING NEW YORK for deptno #10, input row #1, output row #1
10 ACCOUNTING NEW YORK for deptno #20, input row #2, output row #2
20 RESEARCH DALLAS for deptno #30, input row #3, output row #3
20 RESEARCH DALLAS for deptno #40, input row #4, output row #4
30 SALES CHICAGO
30 SALES CHICAGO
40 OPERATIONS BOSTON
40 OPERATIONS BOSTON
Note how the number of output rows (8) does not match the number of input rows (4). Note also the mismatch between DEPTNO values displayed in the output column (INTROSPECTION) and those of the input column (DEPTNO).
This solution works better.
create or replace package body ptf_demo as
function describe(
p_input_table_metadata in out dbms_tf.table_t,
p_replication_factor in pls_integer )
return
dbms_tf.describe_t
as
v_new_column_desc dbms_tf.describe_t ;
begin
p_input_table_metadata.column(1).for_read := true ;
--
-- define a new column
--
v_new_column_desc.new_columns(1).name := 'INTROSPECTION' ;
v_new_column_desc.new_columns(1).type := dbms_tf.type_varchar2 ;
v_new_column_desc.new_columns(1).max_len := 100 ;
-- enable row replication
v_new_column_desc.row_replication := true ;
return v_new_column_desc ;
end describe ;
procedure fetch_rows( p_replication_factor pls_integer ) as
v_for_read_column dbms_tf.tab_number_t ;
v_new_column dbms_tf.tab_varchar2_t ;
v_input_row_number pls_integer ;
v_output_row_number pls_integer := 1 ;
begin
dbms_tf.get_col( 1, v_for_read_column );
--
-- loop through the for_read rows and set new column values
--
v_input_row_number := v_for_read_column.first ;
while v_input_row_number is not null loop
for v_copy_number in 1 .. p_replication_factor loop
v_new_column(v_output_row_number) :=
'for deptno #' || v_for_read_column(v_input_row_number) ||
', input row #' || v_input_row_number ||
', copy #' || v_copy_number ||
', output row #' || v_output_row_number ;
v_output_row_number := v_output_row_number + 1 ;
end loop ;
v_input_row_number := v_for_read_column.next(v_input_row_number) ;
end loop ;
dbms_tf.row_replication( p_replication_factor );
dbms_tf.put_col( 1, v_new_column );
end fetch_rows;
end;
/
select
deptno, dname, loc, introspection
from
ptf_demo.replicate_rows(
p_table => scott.dept,
p_replication_factor => 2 ) ;
DEPTNO DNAME LOC INTROSPECTION
---------- ---------- ---------- ------------------------------------------------------------
10 ACCOUNTING NEW YORK for deptno #10, input row #1, copy #1, output row #1
10 ACCOUNTING NEW YORK for deptno #10, input row #1, copy #2, output row #2
20 RESEARCH DALLAS for deptno #20, input row #2, copy #1, output row #3
20 RESEARCH DALLAS for deptno #20, input row #2, copy #2, output row #4
30 SALES CHICAGO for deptno #30, input row #3, copy #1, output row #5
30 SALES CHICAGO for deptno #30, input row #3, copy #2, output row #6
40 OPERATIONS BOSTON for deptno #40, input row #4, copy #1, output row #7
40 OPERATIONS BOSTON for deptno #40, input row #4, copy #2, output row #8
This time the PTF correctly generated 8 output rows and the DEPTNO values in the output column match the input values.
