Non-leaf Polymorphic Table Functions (PTF)

Controlling Pass Through Columns

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

A PTF can accept an input table and selectively include or exclude its columns in the output. This is controlled via a parameter passed to the implementation package's describe() function. Before we learn how to use this control let's first look at its datatype, dbms_tf.table_t. This datatype, and its releated types, are defined as follows.

TYPE TABLE_T IS RECORD(
  column                TABLE_COLUMNS_T,
  schema_name           DBMS_id,
  package_name          DBMS_id,
  ptf_name              DBMS_id ) ;

  TYPE TABLE_COLUMNS_T IS TABLE OF COLUMN_T ;

    TYPE column_t IS RECORD (
      description            COLUMN_METADATA_T,
      pass_through           BOOLEAN,
      for_read               BOOLEAN);

      TYPE COLUMN_METADATA_T IS RECORD
        ( type               PLS_INTEGER,
          max_len            PLS_integer DEFAULT -1,
          name               VARCHAR2(32767),
          name_len           PLS_INTEGER,
          precision          PLS_INTEGER,
          scale              PLS_INTEGER,
          charsetid          PLS_INTEGER,
          charsetform        PLS_INTEGER,
          collation          PLS_INTEGER ); 

We can visualize the data type like this.

  table_t.schema_name
  table_t.package_name
  table_t.ptf_name
  table_t.column
    table_t.column(i).pass_through
    table_t.column(i).for_read
    table_t.column(i).description
      table_t.column(i).description.type
      table_t.column(i).description.max_len
      table_t.column(i).description.name
      table_t.column(i).description.name_len
      table_t.column(i).description.precision
      table_t.column(i).description.scale
      table_t.column(i).description.charsetid
      table_t.column(i).description.charsetform
      table_t.column(i).description.collation

Note that table_t.column is a nested table. The examples in the DBMS_TF documentation use a FOR loop to iterate through such data types. This is o.k. when the data is densly packed but, as we'll soon see, this might not always be the case. To stay on the safe side the solutions in this tutorial will always iterate through nested tables using a WHILE loop, which works for both dense and sparse data.

Now let's see how to write a describe() function that hides certain columns, e.g. all columns appearing after the first two columns in the input table.

create or replace package ptf_demo as

  function describe( p_input_table_metadata in out dbms_tf.table_t )
    return dbms_tf.describe_t ;

  function trim_columns( p_table table )
    return table pipelined row polymorphic using ptf_demo ;

end;
/

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

    i pls_integer := p_input_table_metadata.column.first ;

  begin

    while i is not null loop

      if i > 2 then

        p_input_table_metadata.column(i).pass_through := false ;  -- hides the column

        end if;

      i := p_input_table_metadata.column.next(i) ;

      end loop ;

    return null ;

    end describe ;

  end ptf_demo ;
/

Let's test it.

select *
from   ptf_demo.trim_columns( scott.dept ) ;
    DEPTNO DNAME
---------- ----------
        10 ACCOUNTING
        20 RESEARCH
        30 SALES
        40 OPERATIONS
select *
from   ptf_demo.trim_columns( scott.emp ) ;
     EMPNO ENAME
---------- ----------
      7839 KING
      7934 MILLER
      7782 CLARK
      7369 SMITH
      7566 JONES
      7876 ADAMS
      7902 FORD
      7788 SCOTT
      7499 ALLEN
      7521 WARD
      7654 MARTIN
      7698 BLAKE
      7844 TURNER
      7900 JAMES

Now let's see how p_table_metadata might become sparsely populated.

rem
rem we use the same package spec created above,
rem only the body changes in this example
rem

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

    i pls_integer ;

  begin

    -- delete columns numbered 2 and 4

    p_input_table_metadata.column.delete(2) ;
    p_input_table_metadata.column.delete(4) ;

    -- now the nested table is sparsely populated

    return null ;

    end describe ;

  end ptf_demo ;
/

For a baseline, let's take a look at the entire EMP table.

select *
from   scott.emp ;
     EMPNO ENAME      JOB               MGR HIREDATE          SAL       COMM     DEPTNO
---------- ---------- ---------- ---------- ---------- ---------- ---------- ----------
      7839 KING       PRESIDENT             1981-11-17       5000                    10
      7934 MILLER     CLERK            7782 1982-01-23       1300                    10
      7782 CLARK      MANAGER          7839 1981-06-09       2450                    10
      7369 SMITH      CLERK            7902 1980-12-17        800                    20
      7566 JONES      MANAGER          7839 1981-04-02       2975                    20
      7876 ADAMS      CLERK            7788 1987-07-13       1100                    20
      7902 FORD       ANALYST          7566 1981-12-03       3000                    20
      7788 SCOTT      ANALYST          7566 1987-07-13       3000                    20
      7499 ALLEN      SALESMAN         7698 1981-02-22       1600        300         30
      7521 WARD       SALESMAN         7698 1981-02-22       1250        500         30
      7654 MARTIN     SALESMAN         7698 1981-09-28       1250       1400         30
      7698 BLAKE      MANAGER          7839 1981-05-01       2850                    30
      7844 TURNER     SALESMAN         7698 1981-09-08       1500          0         30
      7900 JAMES      CLERK            7698 1981-12-03        950                    30

Now let's compare this with the output of the PTF to confirm that columns 2 and 4 are hidden.

select *
from   ptf_demo.trim_columns( scott.emp ) ;
     EMPNO JOB        HIREDATE          SAL       COMM     DEPTNO
---------- ---------- ---------- ---------- ---------- ----------
      7839 PRESIDENT  1981-11-17       5000                    10
      7934 CLERK      1982-01-23       1300                    10
      7782 MANAGER    1981-06-09       2450                    10
      7369 CLERK      1980-12-17        800                    20
      7566 MANAGER    1981-04-02       2975                    20
      7876 CLERK      1987-07-13       1100                    20
      7902 ANALYST    1981-12-03       3000                    20
      7788 ANALYST    1987-07-13       3000                    20
      7499 SALESMAN   1981-02-22       1600        300         30
      7521 SALESMAN   1981-02-22       1250        500         30
      7654 SALESMAN   1981-09-28       1250       1400         30
      7698 MANAGER    1981-05-01       2850                    30
      7844 SALESMAN   1981-09-08       1500          0         30
      7900 CLERK      1981-12-03        950                    30

So far the package works as expected. Let's see what happens if we try to iterate through the collection with a FOR loop now.

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

    -- delete column numbers 2 and 4

    p_input_table_metadata.column.delete(2) ;
    p_input_table_metadata.column.delete(4) ;

    for i in 1 .. p_input_table_metadata.column.count loop

      if p_input_table_metadata.column(i).description.name = '"SAL"' then
        p_input_table_metadata.column(i).pass_through := false ;
        end if;

      end loop ;

    return null ;

    end describe ;

  end ptf_demo ;
/
select *
from   ptf_demo.trim_columns( scott.emp ) ;
Error starting at line : 1 in command -
select *
from   ptf_demo.trim_columns( scott.emp )
Error at Command Line : 2 Column : 8
Error report -
SQL Error: ORA-62565: The Describe method failed with error(s).
ORA-01403: no data found
ORA-06512: at "JFUDA.PTF_DEMO", line 15
ORA-06512: at line 22

We encountered an ORA-01403: no data found exception. If we re-write the package to use a WHILE loop we avoid the exception.

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

    i pls_integer ;

  begin

    -- delete column numbers 2 and 4

    p_input_table_metadata.column.delete(2) ;
    p_input_table_metadata.column.delete(4) ;

    i := p_input_table_metadata.column.first ;

    while i is not null loop

      if p_input_table_metadata.column(i).description.name = '"SAL"' then
        p_input_table_metadata.column(i).pass_through := false ;
        end if;

      i := p_input_table_metadata.column.next(i) ;

      end loop ;

    return null ;

    end describe ;

  end ptf_demo ;
/

This time the query works without error.

select *
from   ptf_demo.trim_columns( scott.emp ) ;
     EMPNO JOB        HIREDATE         COMM     DEPTNO
---------- ---------- ---------- ---------- ----------
      7839 PRESIDENT  1981-11-17                    10
      7934 CLERK      1982-01-23                    10
      7782 MANAGER    1981-06-09                    10
      7369 CLERK      1980-12-17                    20
      7566 MANAGER    1981-04-02                    20
      7876 CLERK      1987-07-13                    20
      7902 ANALYST    1981-12-03                    20
      7788 ANALYST    1987-07-13                    20
      7499 SALESMAN   1981-02-22        300         30
      7521 SALESMAN   1981-02-22        500         30
      7654 SALESMAN   1981-09-28       1400         30
      7698 MANAGER    1981-05-01                    30
      7844 SALESMAN   1981-09-08          0         30
      7900 CLERK      1981-12-03                    30



Revision Notes

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