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.
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...)