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?
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
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