oracle plsql aggregate error: ORA-06502: character string buffer too small
Question
i wrote a simple aggregate function that should work on columns with csv text in them by aggregating distinct values in a resulting csv string. the functions seems to work all the way to the end when it craps out with the ORA-06502 error right when it should be returning the result.
here is the code:
type def:
create or replace type array_union_typ as object (
union_agg nvarchar2(1000),
static function ODCIAggregateInitialize(sctx in out array_union_typ)
return number,
member function ODCIAggregateIterate (self in out array_union_typ,
value in nvarchar2)
return number,
member function ODCIAggregateTerminate (self in array_union_typ,
return_value out nvarchar2,
flags in number)
return number,
member function ODCIAggregateMerge(self in out array_union_typ,
ctx2 in array_union_typ)
return number,
static function agg_union(arg1 in nvarchar2,
arg2 in nvarchar2)
return nvarchar2
);
the body:
create or replace type body array_union_typ is
static function ODCIAggregateInitialize(sctx in out array_union_typ
) return number is
begin
sctx := array_union_typ(null);
return ODCIConst.Success;
end;
member function ODCIAggregateIterate(self in out array_union_typ,
value in nvarchar2
) return number is
begin
union_agg := array_union_typ.agg_union(union_agg, value);
return ODCIConst.Success;
end;
member function ODCIAggregateTerminate(self in array_union_typ,
return_value out nvarchar2,
flags in number
) return number is
begin
dbms_output.put_line('result: '''|| union_agg || ''', length:' || length(union_agg)); --this still prints
return_value := self.union_agg; -- <-- this is where the error is indicated
--return_value := 'x'; -- returning this still gives the error.
return ODCIConst.Success;
end;
member function ODCIAggregateMerge(self in out array_union_typ,
ctx2 in array_union_typ
) return number is
begin
union_agg := array_union_typ.agg_union(union_agg, ctx2.union_agg);
return ODCIConst.Success;
end;
static function agg_union(arg1 in nvarchar2,
arg2 in nvarchar2)
return nvarchar2 is
result nvarchar2(1000);
orig nvarchar2(1000);
begin
dbms_output.enable;
orig := replace(arg1||','||arg2, chr(0));
FOR rec IN (SELECT DISTINCT(regexp_substr(orig, '[^,]+', 1, level)) AS a
FROM dual CONNECT BY regexp_substr(orig, '[^,]+', 1, level) IS NOT NULL ORDER BY a) LOOP
IF result IS NOT NULL THEN
result := result || ',' || rec.a;
ELSE
result := rec.a;
END IF;
END LOOP;
--dbms_output.put_line('endwith: ''' || result || '''');
RETURN substr(result,1,1000);
end;
end;
here is a test table and data:
SQL> desc uniontest
Name Null? Type
----------------------------------------- -------- ----------------------------
I NVARCHAR2(50)
SQL> select * from uniontest;
I
--------------------------------------------------
a
a
b,c
b,d,e
and finally, this is what happens if i try to use the aggregate function:
SQL> select array_union(i) from uniontest;
select array_union(i) from uniontest
*
ERROR at line 1:
ORA-06502: PL/SQL: numeric or value error: character string buffer too small
ORA-06512: at "M35456.ARRAY_UNION_TYP", line 25
result: 'a,b,c,d,e', length:9
if i simply pass a single character string like 'x' in the offending line, i still get the same error. only on a null result does it go away. i am stumped and out of ideas.
thanks for any help.
btw, if anyone has any idea why i get added \0 characters i my agg_union function parameters, i am dying to know about that, too.
Solution
The ODCIaggregate
methods don't seem to be playing nicely with nvarchar2
; you don't appear to be doing anything wrong since it works fine if those are all changed to varchar2
.
If you are on 11gR2 and thus have the listagg
function available - but currently aren't using that because you need to figure out the distinct values - you can combine it with your existing regexp_substr
function, so you don't need your own type/function:
SELECT REPLACE(LISTAGG(a, ',') WITHIN GROUP (ORDER BY a), chr(0), '')
FROM (
SELECT DISTINCT(regexp_substr(i, '[^,]+', 1, level)) AS a
FROM uniontest CONNECT BY regexp_substr(i, '[^,]+', 1, level) IS NOT NULL
ORDER BY a
);
... which with your data gives:
REPLACE(LISTAGG(A,',')WITHINGROUP(ORDERBYA),CHR(0),'')
------------------------------------------------------
a,b,c,d,e
In earlier versions you can use SELECT REPLACE(WM_CONCAT(a), chr(0), '')
instead.
(The chr(0)
here and the \0
in your question seem to be related to nvarchar2
, but someone else will need to chip in on the details...)
OTHER TIPS
If I'm not mistaken this
SELECT DISTINCT(regexp_substr(orig, '[^,]+', 1, level)) AS a
FROM dual CONNECT BY regexp_substr(orig, '[^,]+', 1, level) IS NOT NULL ORDER BY a)
results in an infinite number of rows, since the CONNECT BY
condition is not dependend on the row.
Since you loop over the result appending characters to a string sooner the string becomes to large.