Skip to Main Content
  • Questions
  • Polymorphic Table Function: TIMESTAMP(9) column loses fractional-second precision (rounded to 6) and TO_CHAR(...FF9) returns zeros

Breadcrumb

Question and Answer

Connor McDonald

Thanks for the question, Łukasz.

Asked: February 07, 2026 - 5:21 pm UTC

Last updated: February 26, 2026 - 8:05 am UTC

Version: 19c

Viewed 100+ times

You Asked

I’m using a Polymorphic Table Function (PTF) and adding a new column TIMESTAMP_CALCULATED of type TIMESTAMP with fractional seconds scale 9. In FETCH_ROWS I populate it with a constant value:

2026-01-01 01:01:01.123456789

However, when selecting the PTF output, the value appears rounded to 6 fractional digits, e.g.:

2026-01-01T01:01:01.123457Z (rounded to microseconds)

Additionally, formatting it with:
to_char(TIMESTAMP_CALCULATED,'YYYY-MM-DD HH24:MI:SS.FF9')

produces:

2026-01-01 01:01:01.000000000

This is unexpected: even if the value was rounded to 6 digits, TO_CHAR(...FF9) should not become all zeros.

Reproducible test case (minimal script)
CREATE TABLE TIMESTAMP_INPUT(ID INTEGER, TIMESTAMP_VALUE TIMESTAMP(9));

CREATE OR REPLACE PACKAGE TIMESTAMP_PTF AS

FUNCTION describe (tab IN OUT DBMS_TF.table_t) RETURN DBMS_TF.describe_t;
PROCEDURE fetch_rows;

FUNCTION my_ptf(tab IN TABLE) RETURN TABLE PIPELINED ROW POLYMORPHIC USING TIMESTAMP_PTF;

END;
/

CREATE OR REPLACE PACKAGE BODY TIMESTAMP_PTF AS

FUNCTION describe (tab IN OUT DBMS_TF.table_t) RETURN DBMS_TF.describe_t IS
  new_cols dbms_tf.columns_new_t; 
BEGIN
    new_cols(1) := dbms_tf.column_metadata_t(name => 'TIMESTAMP_CALCULATED', type => dbms_tf.type_timestamp, scale => 9); 
    RETURN dbms_tf.describe_t(new_columns => new_cols);
END describe;

PROCEDURE fetch_rows 
as 
  v_rowset dbms_tf.row_set_t; 
  v_vals_rowset dbms_tf.row_set_t; 
   
  v_row_count binary_integer; 
  v_col_count binary_integer; 
begin 
  dbms_tf.get_row_set(v_rowset, v_row_count, v_col_count); 
  
  for i in 1..v_row_count loop 
    v_vals_rowset(1).tab_timestamp(i) := TO_TIMESTAMP('2026-01-01 01:01:01.123456789','YYYY-MM-DD HH24:MI:SS.FF9');
  end loop;

  dbms_tf.put_row_set(v_vals_rowset); 
end; 

END;
/

DELETE TIMESTAMP_INPUT;
INSERT INTO TIMESTAMP_INPUT(ID, TIMESTAMP_VALUE) 
  VALUES(1, TO_TIMESTAMP('2026-01-01 01:01:01.123456789','YYYY-MM-DD HH24:MI:SS.FF9'));

INSERT INTO TIMESTAMP_INPUT(ID, TIMESTAMP_VALUE) 
  VALUES(2, TO_TIMESTAMP('2026-01-02 02:02:02.123456789','YYYY-MM-DD HH24:MI:SS.FF9'));

INSERT INTO TIMESTAMP_INPUT(ID, TIMESTAMP_VALUE) 
  VALUES(3, TO_TIMESTAMP('2026-01-03 03:03:03.123456789','YYYY-MM-DD HH24:MI:SS.FF9'));  
COMMIT;  

ALTER SESSION SET NLS_TIMESTAMP_FORMAT = 'YYYY-MM-DD HH24:MI:SS.FF9';
ALTER SESSION SET NLS_TIMESTAMP_TZ_FORMAT = 'YYYY-MM-DD HH24:MI:SS.FF9 TZH:TZM';

WITH BASE_DATA AS(
    SELECT ID, TIMESTAMP_VALUE FROM TIMESTAMP_INPUT
)
SELECT id, 
       to_char(TIMESTAMP_VALUE,'YYYY-MM-DD HH24:MI:SS.FF9') TIMESTAMP_VALUE_STR,
       DUMP(TIMESTAMP_VALUE) dump_TIMESTAMP_VALUE, 
       TIMESTAMP_VALUE,
       to_char(TIMESTAMP_CALCULATED,'YYYY-MM-DD HH24:MI:SS.FF9') TIMESTAMP_CALCULATED_STR,
       DUMP(TIMESTAMP_CALCULATED) DUMP_TIMESTAMP_CALCULATED,
       TIMESTAMP_CALCULATED
  FROM TIMESTAMP_PTF.my_ptf(BASE_DATA) pf;

drop table TIMESTAMP_INPUT;
drop package TIMESTAMP_PTF;


Actual results (what I see):
TIMESTAMP_CALCULATED_STR DUMP_TIMESTAMP_CALCULATED TIMESTAMP_CALCULATED
------------------------------- ------------------------------------------------ -----------------------------
2026-01-01 01:01:01.000000000 Typ=180 Len=11: 120,126,1,1,2,2,2,7,91,205,232 2026-01-01T01:01:01.123457Z
2026-01-01 01:01:01.000000000 Typ=180 Len=11: 120,126,1,1,2,2,2,7,91,205,232 2026-01-01T01:01:01.123457Z
2026-01-01 01:01:01.000000000 Typ=180 Len=11: 120,126,1,1,2,2,2,7,91,205,232 2026-01-01T01:01:01.123457Z

Expected results:
TIMESTAMP_CALCULATED should preserve scale 9 (nanoseconds) if the column is defined as TIMESTAMP(9).
TO_CHAR(...FF9) should display the stored fractional seconds, not zeros.

Questions:
How can I force/guarantee TIMESTAMP(9) precision for a new column created in a PTF (and populated via TAB_TIMESTAMP) so that I can reliably display FF9?

Is there a known limitation/bug in PTF/DBMS_TF regarding TIMESTAMP(9) (or metadata propagation) causing rounding to 6 digits and/or TO_CHAR returning zeros?

If this is expected behavior, what is the recommended workaround to preserve nanosecond precision?

with LiveSQL Test Case:
https://freesql.com/?compressed_code=H4sIAAAAAAAACr1V227iMBB9j5R%252FmDcSKbBAd7tbUFcKiSnR5qbY2Yq%252BWCGYNlpIImJa9e9XNpfQlNJKexFGJMzMmXNm7LEVIZMgIObIRUAcD2FieiF1%252FDAmmmOD4xN0gyLjyPbTdOMjX%252B1K14eqIj7WFiyIIEKha1oIQtP6Yd4cI4dkDCYW3uPYt4gT%252BDBnVbrOZgw0nszA8SGICdgjD1My7vBktmSU6xAhEkf%252B4f99EOVDVQmjwEJ2HCFYMJ4%252B0HXxVA1f5Fg905Iv9gmk3APkVnzohMh1fGRDFNxCGLhTL4jCiWNBjB3%252F5qUECY58e6gqn84rHwX29J%252FKBwerCkDOnmhaLCuYz1YV5YtOWiw3q7yiwsCHoCojdOP4wrV21no6DK4bIXTFeDJPeEK5licrBtffoVUrsEzXil2TILtlAH8upX2PIN4pz1as4smqNKBKk6V0uNIFBZF7J2QfUQvRdqwEaxGyJyl2F%252FLtQ51k7U91HFQlqWSWR%252FnO%252BCHLuniiFePbQgj7Y7Ks3nGqkWhabHIOsyxP1s80yzm7Z%252Bs9Ulos37TP2H2WS799jnvG6S6PtmdpHGcxjiF3RRPfRbGGDLIcep3OMallUZS7yr5QpfV0sXnqZmiZ7DUJaH10W%252F1u%252F7Ld7bW7Pej2BnJ1ev2Lz18uv367ahmt6XQ6bXte27ZhMul%252FHnjOAOPOeHzVEl0BYPlcMpA9qVWWm2OVR6ykIJbPxc%252BLA2QjF5FXE2ioKo6PUUTEHApOzKdXc0mXtZCPWOsZf0%252Fudsb9CZv%252Bm2z60O0P5Pp%252FbC7eZHMB3YuBXB9nI%252FaoFXieQ%252BSjqpguQRFghLEYcRgR8F1cp6PjIPJMAtdwBnf4Pgy5%252BwgSkLvJgNx5LVm1W4dMYGRiRG2TmGBibXt8MHKRReBE4WAcBV6zwqqiq8ouJpsbuzMIALyg6UOy1hogZyvYzEgxiYwDoh17YRNOh%252FlmVdJmkppG03KGXz3SP0qyjjjLtHbTpYmezHmCcm0Vxkb5QzLu7O7zQxt1KBeyu%252FN1UYK8NU8ME2ksk%252FRXcs8aFzpAu40C6zdP%252FFZ0EAkAAA%253D%253D&code_language=PL_SQL&db_version=23&code_format=false

and Connor said...

I've replicated your issue on 19c, 21c and on the latest 26ai, so I do feel its a bug.

I'd encourage you to log an SR with your test case. ( I can log the bug if you like, but bugs carry more weight if they come from a customer SR).

I suppose you could carry the content as a string and convert later as a workaround, but I fully concede that's a kludge

SQL> CREATE OR REPLACE PACKAGE BODY TIMESTAMP_PTF AS
  2
  3  FUNCTION describe (tab IN OUT DBMS_TF.table_t) RETURN DBMS_TF.describe_t IS
  4    new_cols dbms_tf.columns_new_t;
  5  BEGIN
  6      new_cols(1) := dbms_tf.column_metadata_t(name => 'TIMESTAMP_CALCULATED', type => dbms_tf.type_varchar2);
  7      RETURN dbms_tf.describe_t(new_columns => new_cols);
  8  END describe;
  9
 10  PROCEDURE fetch_rows
 11  as
 12    v_rowset dbms_tf.row_set_t;
 13    v_vals_rowset dbms_tf.row_set_t;
 14
 15    v_row_count binary_integer;
 16    v_col_count binary_integer;
 17
 18  begin
 19    dbms_tf.get_row_set(v_rowset, v_row_count);
 20
 21    for i in 1..v_row_count loop
 22      v_vals_rowset(1).tab_varchar2(i) := '2026-01-01 01:01:01.123456789';
 23    end loop;
 24
 25    dbms_tf.put_row_set(v_vals_rowset);
 26  end;
 27
 28  END;
 29  /

Package body created.

SQL> SELECT id,
  2         TIMESTAMP_VALUE,
  3         to_timestamp(TIMESTAMP_CALCULATED,'YYYY-MM-DD HH24:MI:SS.FF9') TIMESTAMP_CALCULATED
  4    FROM TIMESTAMP_PTF.my_ptf(t) pf;

        ID TIMESTAMP_VALUE                          TIMESTAMP_CALCULATED
---------- ---------------------------------------- ----------------------------------------
         1 2023-01-01 01:01:01.123456789            2026-01-01 01:01:01.123456789



More to Explore

PL/SQL demos

Check out more PL/SQL tutorials on our LiveSQL tool.

PL/SQL docs

PL/SQL reference manual from the Oracle documentation library