So far in this tutorial we have only explored "Row Semantics" polymorphic table functions, i.e. those defined with a PIPELINED ROW clause. In this module we explore "Table Semantics" polymorphic table functions, i.e. those defined with a PIPELINED TABLE clause. Table Semantics PTFs allow us to specify PARTITION BY and ORDER BY clauses (like those available with Oracle analytic functions) when invoking a PTF.
Let's create both types of functions and take them for a spin.
create or replace package ptf_demo as function describe( p_input_table_metadata in out dbms_tf.table_t ) return dbms_tf.describe_t ; -- this is the Table Semantics PTF function table_semantics( p_table table ) return table PIPELINED TABLE polymorphic using ptf_demo ; -- we'll also create a row semantics version for comparison function row_semantics( 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 begin return null; end describe ; end ptf_demo; /
Now we run both PTFs to see what they return.
select deptno, ename from ptf_demo.row_semantics( scott.emp );
DEPTNO ENAME ---------- ---------- 10 KING 10 MILLER 10 CLARK 20 SMITH 20 JONES 20 ADAMS 20 FORD 20 SCOTT 30 ALLEN 30 WARD 30 MARTIN 30 BLAKE 30 TURNER 30 JAMES
select deptno, ename from ptf_demo.table_semantics( scott.emp );
DEPTNO ENAME ---------- ---------- 10 KING 10 MILLER 10 CLARK 20 SMITH 20 JONES 20 ADAMS 20 FORD 20 SCOTT 30 ALLEN 30 WARD 30 MARTIN 30 BLAKE 30 TURNER 30 JAMES
Note how the output of both SELECT commands is the same.
Let's try using PARTITION BY and ORDER BY clauses with ROW_SEMANTICS().
select deptno, ename from ptf_demo.row_semantics( scott.emp PARTITION BY DEPTNO ORDER BY ENAME ) order by deptno, ename asc ;
Error starting at line : 1 in command - select deptno, ename from ptf_demo.row_semantics( scott.emp PARTITION BY DEPTNO ORDER BY ENAME ) order by deptno, ename asc Error at Command Line : 2 Column : 55 Error report - SQL Error: ORA-62568: invalid use of PARTITION BY or ORDER BY clause
It does not work with ROW_SEMANTICS(). We will have better luck with TABLE_SEMANTICS().
select deptno, ename from ptf_demo.table_semantics( scott.emp PARTITION BY DEPTNO ORDER BY ENAME DESC ) order by deptno, ename asc ;
DEPTNO ENAME ---------- ---------- 10 CLARK 10 KING 10 MILLER 20 ADAMS 20 FORD 20 JONES 20 SCOTT 20 SMITH 30 ALLEN 30 BLAKE 30 JAMES 30 MARTIN 30 TURNER 30 WARD
In the absence of any useful FETCH_ROWS() logic the output is unremarkable however. Let's continue exploring.
PARTITION BY
To better understand how the PARTITION BY clause affects execution we take a look at what happens internally.
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 table_semantics( p_table table ) return table PIPELINED TABLE polymorphic using ptf_demo ; function row_semantics( 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 -- -- make ENAME available to get_row_set() -- p_input_table_metadata.column(2).for_read := true ; -- -- create a new output column -- v_new_column_desc.new_columns(1).name := 'INTROSPECTION' ; v_new_column_desc.new_columns(1).type := dbms_tf.type_varchar2 ; return v_new_column_desc ; end describe ; procedure fetch_rows as v_input_column dbms_tf.tab_varchar2_t ; v_output_column dbms_tf.tab_varchar2_t ; i pls_integer ; begin dbms_tf.get_col( 1, v_input_column ); for i in 1 .. dbms_tf.get_env().row_count loop v_output_column(i) := 'Row Count = ' || dbms_tf.get_env().row_count || ', ' || 'First ENAME in the row set = ' || v_input_column(1) ; end loop ; dbms_tf.put_col( 1, v_output_column ); end fetch_rows ; end ptf_demo ; /
select deptno, ename, introspection from ptf_demo.row_semantics( scott.emp ) ;
DEPTNO ENAME INTROSPECTION ---------- ---------- ------------------------------------------------------------ 10 KING Row Count = 14, First ENAME in the row set = KING 10 MILLER Row Count = 14, First ENAME in the row set = KING 10 CLARK Row Count = 14, First ENAME in the row set = KING 20 SMITH Row Count = 14, First ENAME in the row set = KING 20 JONES Row Count = 14, First ENAME in the row set = KING 20 ADAMS Row Count = 14, First ENAME in the row set = KING 20 FORD Row Count = 14, First ENAME in the row set = KING 20 SCOTT Row Count = 14, First ENAME in the row set = KING 30 ALLEN Row Count = 14, First ENAME in the row set = KING 30 WARD Row Count = 14, First ENAME in the row set = KING 30 MARTIN Row Count = 14, First ENAME in the row set = KING 30 BLAKE Row Count = 14, First ENAME in the row set = KING 30 TURNER Row Count = 14, First ENAME in the row set = KING 30 JAMES Row Count = 14, First ENAME in the row set = KING
The output above tells us that FETCH_ROWS() is only called once when executing ROW_SEMANTICS().
select deptno, ename, introspection from ptf_demo.table_semantics( scott.emp ) ;
DEPTNO ENAME INTROSPECTION ---------- ---------- ------------------------------------------------------------ 10 KING Row Count = 14, First ENAME in the row set = KING 10 MILLER Row Count = 14, First ENAME in the row set = KING 10 CLARK Row Count = 14, First ENAME in the row set = KING 20 SMITH Row Count = 14, First ENAME in the row set = KING 20 JONES Row Count = 14, First ENAME in the row set = KING 20 ADAMS Row Count = 14, First ENAME in the row set = KING 20 FORD Row Count = 14, First ENAME in the row set = KING 20 SCOTT Row Count = 14, First ENAME in the row set = KING 30 ALLEN Row Count = 14, First ENAME in the row set = KING 30 WARD Row Count = 14, First ENAME in the row set = KING 30 MARTIN Row Count = 14, First ENAME in the row set = KING 30 BLAKE Row Count = 14, First ENAME in the row set = KING 30 TURNER Row Count = 14, First ENAME in the row set = KING 30 JAMES Row Count = 14, First ENAME in the row set = KING
The query above tells us FETCH_ROWS() is also only called once when executing TABLE_SEMANTICS() with no PARTITION BY clause.
select deptno, ename, introspection from ptf_demo.table_semantics( scott.emp PARTITION BY DEPTNO ) order by deptno, ename ;
DEPTNO ENAME INTROSPECTION ---------- ---------- ------------------------------------------------------------ 10 CLARK Row Count = 3, First ENAME in the row set = CLARK 10 KING Row Count = 3, First ENAME in the row set = CLARK 10 MILLER Row Count = 3, First ENAME in the row set = CLARK 20 ADAMS Row Count = 5, First ENAME in the row set = ADAMS 20 FORD Row Count = 5, First ENAME in the row set = ADAMS 20 JONES Row Count = 5, First ENAME in the row set = ADAMS 20 SCOTT Row Count = 5, First ENAME in the row set = ADAMS 20 SMITH Row Count = 5, First ENAME in the row set = ADAMS 30 ALLEN Row Count = 6, First ENAME in the row set = ALLEN 30 BLAKE Row Count = 6, First ENAME in the row set = ALLEN 30 JAMES Row Count = 6, First ENAME in the row set = ALLEN 30 MARTIN Row Count = 6, First ENAME in the row set = ALLEN 30 TURNER Row Count = 6, First ENAME in the row set = ALLEN 30 WARD Row Count = 6, First ENAME in the row set = ALLEN
The third query reveals that adding a PARTITION BY clause causes FETCH_ROWS() to be called once per distinct partition key value.
ORDER BY
As with analytic functions the ORDER BY clause in the PTF table argument controls the input row set order. The SELECT command's ORDER BY clause controls the output row set order. The following example demonstrates both clauses.
select deptno, ename, introspection from ptf_demo.table_semantics( scott.emp partition by deptno ORDER BY ENAME DESC ) ORDER BY DEPTNO, ENAME ASC ;
DEPTNO ENAME INTROSPECTION ---------- ---------- ------------------------------------------------------------ 10 CLARK Row Count = 3, First ENAME in the row set = MILLER 10 KING Row Count = 3, First ENAME in the row set = MILLER 10 MILLER Row Count = 3, First ENAME in the row set = MILLER 20 ADAMS Row Count = 5, First ENAME in the row set = SMITH 20 FORD Row Count = 5, First ENAME in the row set = SMITH 20 JONES Row Count = 5, First ENAME in the row set = SMITH 20 SCOTT Row Count = 5, First ENAME in the row set = SMITH 20 SMITH Row Count = 5, First ENAME in the row set = SMITH 30 ALLEN Row Count = 6, First ENAME in the row set = WARD 30 BLAKE Row Count = 6, First ENAME in the row set = WARD 30 JAMES Row Count = 6, First ENAME in the row set = WARD 30 MARTIN Row Count = 6, First ENAME in the row set = WARD 30 TURNER Row Count = 6, First ENAME in the row set = WARD 30 WARD Row Count = 6, First ENAME in the row set = WARD
Replication
The fact that FETCH_ROWS() executes once per distinct partition key value means we can set the replication factor to a different value for each partition, as demonstrated next.
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 -- -- make DEPTNO available to get_row_set() -- p_input_table_metadata.column(8).for_read := true ; -- -- create a new output 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.row_replication := true ; return v_new_column_desc ; end describe ; procedure fetch_rows as v_deptno dbms_tf.tab_number_t ; v_introspection dbms_tf.tab_varchar2_t ; v_input_row_number pls_integer ; v_output_row_number pls_integer := 1 ; v_replication_factor naturaln := 1 ; begin dbms_tf.get_col( 1, v_deptno ); -- -- let's replicate once for each DEPTNO 10 row, -- twice for each DEPTNO 20 row, and three times for -- each DEPTNO 30 row -- v_replication_factor := v_deptno(1) / 10 ; -- -- assemble the output rows -- v_input_row_number := v_deptno.first ; while v_input_row_number is not null loop for v_copy_number in 1 .. v_replication_factor loop v_introspection(v_output_row_number) := 'Replication factor = ' || v_replication_factor ; v_output_row_number := v_output_row_number + 1 ; end loop ; v_input_row_number := v_deptno.next(v_input_row_number) ; end loop ; dbms_tf.row_replication( v_replication_factor ) ; dbms_tf.put_col( 1, v_introspection ); end fetch_rows ; end ptf_demo ; / select deptno, ename, introspection from ptf_demo.table_semantics( scott.emp partition by deptno order by ename ) order by deptno, ename ;
DEPTNO ENAME INTROSPECTION ---------- ---------- ------------------------------------------------------------ 10 CLARK Replication factor = 1 10 KING Replication factor = 1 10 MILLER Replication factor = 1 20 ADAMS Replication factor = 2 20 ADAMS Replication factor = 2 20 FORD Replication factor = 2 20 FORD Replication factor = 2 20 JONES Replication factor = 2 20 JONES Replication factor = 2 20 SCOTT Replication factor = 2 20 SCOTT Replication factor = 2 20 SMITH Replication factor = 2 20 SMITH Replication factor = 2 30 ALLEN Replication factor = 3 30 ALLEN Replication factor = 3 30 ALLEN Replication factor = 3 30 BLAKE Replication factor = 3 30 BLAKE Replication factor = 3 30 BLAKE Replication factor = 3 30 JAMES Replication factor = 3 30 JAMES Replication factor = 3 30 JAMES Replication factor = 3 30 MARTIN Replication factor = 3 30 MARTIN Replication factor = 3 30 MARTIN Replication factor = 3 30 TURNER Replication factor = 3 30 TURNER Replication factor = 3 30 TURNER Replication factor = 3 30 WARD Replication factor = 3 30 WARD Replication factor = 3 30 WARD Replication factor = 3
Beware of bug 28165108. Code that encounters a replication factor of 0 may produce incorrect output until this bug is fixed.