0

Reference Tutorial- oracle-developer.net

Below is my working Oracle query-

-- User-Defined TYPE
create or replace TYPE varchar2_ntt AS TABLE OF VARCHAR2(4000);


-- User-Defined FUNCTION
CREATE FUNCTION to_string (
                    nt_in        IN varchar2_ntt,
                    delimiter_in IN VARCHAR2 DEFAULT ','
                    ) RETURN VARCHAR2 IS
  
       v_idx PLS_INTEGER;
       v_str VARCHAR2(32767);
       v_dlm VARCHAR2(10);
  
   BEGIN
 
      v_idx := nt_in.FIRST;
      WHILE v_idx IS NOT NULL LOOP
         v_str := v_str || v_dlm || nt_in(v_idx);
         v_dlm := delimiter_in;
         v_idx := nt_in.NEXT(v_idx);
      END LOOP;
 
      RETURN v_str;
 
   END to_string;


-- Simple Example
SELECT deptno,
        TO_STRING(CAST(COLLECT(ename) AS varchar2_ntt)) AS emps
FROM   emp
    GROUP BY  deptno;

I'm trying to implement Collect aggregate function on multiple columns. It works fine, without my user-defined function TO_STRING.

-- This works
select emp.DEPT_NAME,
        CAST(
            COLLECT(emp.EMP_ID || ':' || emp.EMP_JOIN_DATE) 
        AS varchar2_ntt) AS EMPS
from  employee emp 
    group by  emp.DEPT_NAME;
    
-- Output
DEPT_NAME   EMPS

SALES       TEST_DB.VARCHAR2_NTT('750127:20-JAN-23', '750228:20-JAN-23')
FINANCE     TEST_DB.VARCHAR2_NTT('548834:10-JAN-19', '802850:14-MAR-23', '802849:19-OCT-23')

But when I try to implement TO_STRING on cast(collect(...)), it fails.

--This doesn't work 
select emp.DEPT_NAME,
        TO_STRING(CAST(
            COLLECT(emp.EMP_ID || ':' || emp.EMP_JOIN_DATE) 
        AS varchar2_ntt)) AS EMPS
from  employee emp 
    group by  emp.DEPT_NAME;

Error:

ORA-06502: PL/SQL: numeric or value error: character string buffer too small
06502. 00000 -  "PL/SQL: numeric or value error%s"
*Cause:    An arithmetic, numeric, string, conversion, or constraint error
           occurred. For example, this error occurs if an attempt is made to
           assign the value NULL to a variable declared NOT NULL, or if an
           attempt is made to assign an integer larger than 99 to a variable
           declared NUMBER(2).
*Action:   Change the data, how it is manipulated, or how it is declared so
           that values do not violate constraints.

How do I solve this Error?

I'm able to achieve the above using LISTAGG & XMLAGG. But in terms of performance, COLLECT is more efficient than these 2. LISTAGG has size limitations while XMLAGG is very slow.

2
  • 1
    Your code works fiddle.
    – MT0
    Commented Nov 4, 2023 at 13:29
  • I deleted my previous comment. It does work with multiple columns, the issue here was the SIZE, which is now resolved after implementing the solution provided by you. Commented Nov 4, 2023 at 14:28

1 Answer 1

1

In the SQL scope, VARCHAR2 is limited to 4000 bytes.

In the PL/SQL scope, VARCHAR2 can be up to 32767 bytes.

If you try to return a string that is too long from a PL/SQL scope to an SQL scope then you will get an ORA-06502: PL/SQL: numeric or value error: character string buffer too small exception.

If your strings are too long then do not return VARCHAR2 and return a CLOB.

CREATE OR REPLACE FUNCTION to_string (
  nt_in        IN varchar2_ntt,
  delimiter_in IN VARCHAR2 DEFAULT ','
) RETURN CLOB
IS
  v_idx PLS_INTEGER;
  v_str CLOB;
BEGIN
  IF nt_in IS NULL THEN
    RETURN NULL;
  END IF;
  v_str := EMPTY_CLOB();
  IF nt_in IS EMPTY THEN
    RETURN v_str;
  END IF;
  v_idx := nt_in.FIRST;
  v_str := nt_in(v_idx);
  LOOP
    v_idx := nt_in.NEXT(v_idx);
    EXIT WHEN v_idx IS NULL;
    v_str := v_str || delimiter_in || nt_in(v_idx);
  END LOOP;
  RETURN v_str;
END to_string;
/

fiddle

3
  • OMG, it worked. You rock @MTO, infinite Thanks to you :). Other than the CLOB part, other changes that you have done to to_string, initially it looked complicated. But upon a closer look, your changes totally make sense. Just one question here. The extra nt_in IS NULL & nt_in IS EMPTY checks that you have added, are they going to affect the overall performance? Since we're dealing with very large data. Commented Nov 4, 2023 at 14:03
  • 1
    @satyaprakashPanigrahi If your collection is never going to be NULL and never going to be empty then you can eliminate those checks. However, if you do have either of those conditions then short-cutting the logic in those cases is possibly going to be more efficient when it does occur (but the difference is going to be minor as any difference is going to be obscured by generating the collections or looping through the collections). You can profile it and see.
    – MT0
    Commented Nov 4, 2023 at 14:16
  • Got it, simple null/empty checks are not going to cost any extra overhead, or negligible if any. Thanks once again @MTO. And yes, I will do the profiling to have a clear picture. Commented Nov 4, 2023 at 14:26

Not the answer you're looking for? Browse other questions tagged or ask your own question.