Non-leaf Polymorphic Table Functions (PTF)

Table Semantics

Applies to: Oracle 18c, Tested against: Oracle 18.1

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.




Revision Notes

Date Category Note
2018-06-23 Created Added to site