function dynamic fetch in Oracle/PLSQL
In coding, we often meet some sql queries like ' select many cloumns from tablename where condition; '. When the number of cloumns is dynamic, we should use ResultSet to achieve in Java. As we all know, the class ResultSet has been defined in Java class library. We can use it to get the result of query with optional cloumns directly. However, we are now in Oracle/PLSQL, and if we want to get the same effect as Java, we should get one better way to solve that. You may think about using cursor to achieve you want. Actually, you will realize that's not exactly what you think. There is one solution for what we said, the following function makes full use of cursor to get what we want. And it has been verified on Oracle DB. I really wish this func can help you all.
create or replace function func_dynamic_fetch ( p_sql in varchar2, p_no_of_cols integer, p_return_multiple_row boolean := true) return anydatalist is lc_rs sys_refcursor; strlist varchar2List := varchar2List(); n number := 3; code varchar2(32767); TYPE ref_cursor IS REF CURSOR; rc_ ref_cursor; c_ NUMBER; i_ NUMBER; col_count_ NUMBER; desc_tab_ DBMS_SQL.DESC_TAB2; col_num NUMBER; n2 number; l_any_list anydatalist; l_types varchar2list; l_add_stmts varchar2list; l_fetch_type varchar2(32767); c_date_type constant number := 12; c_timestamp_type constant number := 180; function repeatStmt(n in integer, template in varchar2, seperator in varchar2 := '', vals in varchar2list := null) return varchar2 is l_result varchar2(32767); l_stmt varchar2(32767); begin for i in 1..n loop l_stmt := template; if vals is not null then l_stmt := replace(template, '__val__', vals(i)); end if; l_result := l_result || replace(l_stmt, '__$__', i); if i < n then l_result := l_result || seperator; end if; end loop; return l_result; end repeatStmt; begin open rc_ for p_sql; n2 := p_no_of_cols; c_ := DBMS_SQL.to_cursor_number(rc_); DBMS_SQL.DESCRIBE_COLUMNS2(c_, col_count_, desc_tab_); col_num := desc_tab_.first; l_types := varchar2list(); l_add_stmts := varchar2list(); if col_num is not null then loop if desc_tab_(col_num).col_type = c_date_type or desc_tab_(col_num).col_type =c_timestamp_type then varchar2listAdd(l_types, 'date'); --to conform to jdbc date format varchar2listAdd(l_add_stmts, 'varchar2ListAdd(l_str_list, to_char(a__$__, ''yyyy-mm-dd hh:mi:ss''));'); else varchar2listAdd(l_types, 'varchar2(32767)'); varchar2listAdd(l_add_stmts, 'varchar2ListAdd(l_str_list, a__$__);'); end if; col_num:= desc_tab_.next(col_num); exit when (col_num is null); end loop; end if; --dbms_output.put_line('cursor count: ' || col_count_); n := col_count_; if n2 > n then n2 := n; end if; --dbms_output.put_line('number of columns: ' || n ); --dbms_output.put_line('number of columns to fetch: ' || n2 ); dbms_sql.close_cursor(c_); --if rc_%isopen then -- close rc_; --end if; code := ' declare lc_rs sys_refcursor; l_sql varchar2(32767); l_str_list varchar2list; l_counter integer; l_any_list anydatalist; ' || repeatStmt(n, 'a__$__ __val__; ', seperator => chr(10), vals => l_types) || ' begin l_sql := :0; --dbms_output.put_line(l_sql); open lc_rs for l_sql; l_any_list := anydatalist(); l_counter := 0; loop fetch lc_rs into ' || repeatStmt(n, 'a__$__', ', ') || ';' || ' exit when lc_rs%notfound; l_counter := l_counter + 1; l_str_list := varchar2List();' || --repeatStmt(n2, 'varchar2ListAdd(l_str_list, a__$__);', chr(10), vals => l_types) || ' repeatStmt(n2, '__val__', chr(10), vals => l_add_stmts) || ' anydataListAdd(l_any_list, anydata.convertCollection(l_str_list)); ' || (case when p_return_multiple_row = false then 'exit when l_counter = 1;' end) || ' end loop; if lc_rs%isopen then close lc_rs; end if; :1 := l_any_list; exception when others then dbms_output.put_line('' func_dynamic_fetch exec immediate exception: '' || sqlerrm); raise; if lc_rs%isopen then close lc_rs; end if; end; ' ; --dbms_output.put_line(code); execute immediate code using p_sql, out l_any_list; return l_any_list; exception when others then if rc_%isopen then close rc_; end if; dbms_output.put_line('func_dynamic_fetch: exception: ' || sqlerrm); return anydatalist(); end; / show errors;
Test case:
DECLARE v_sql VARCHAR2(1024) := 'select a.code,a.prod_code,a.alloc_qty from uni_allotresultitem a where a.id = 204'; v_anydatalist anydatalist; v_varchar2list varchar2list := varchar2list(); v_anydata anydata; v_vnt NUMBER(10); BEGIN v_anydatalist := func_dynamic_fetch(v_sql, 2, TRUE); FOR i IN 1 .. v_anydatalist.count LOOP v_anydata := v_anydatalist(i); v_vnt := v_anydata.getcollection(v_varchar2list); FOR j IN 1 .. v_varchar2list.count LOOP dbms_output.put_line(i||':v_varchar2list(' || j || '):' || v_varchar2list(j)); END LOOP; END LOOP; END;
Test result:
Related type:
CREATE OR REPLACE TYPE AnydataList FORCE IS VARRAY (2147483647) OF ANYDATA ; / CREATE OR REPLACE PROCEDURE AnydataListAdd (list IN OUT NOCOPY AnydataList , v IN ANYDATA ) IS BEGIN list.extend ; list (list.count ) := v ; END ; / CREATE OR REPLACE TYPE Varchar2List FORCE IS VARRAY (2147483647) OF VARCHAR2 (32767) ; / CREATE OR REPLACE PROCEDURE Varchar2ListAdd (list IN OUT NOCOPY Varchar2List , v IN VARCHAR2 ) IS BEGIN list.extend ; list (list.count ) := v ; END ;