PL/SQL: varchar2 as IN parameter to function not working - oracle

I have a PL/SQL function like this:
FUNCTION find_by_items(items IN varchar2)
RETURN SYS_REFCURSOR
AS
o_cursor SYS_REFCURSOR;
BEGIN
open o_cursor for select a.something from my_table a where a.item in (items);
return o_cursor;
END find_by_items;
This always returns an empty cursor.
If I run it with, for example:
select find_by_items('''one'', ''two'', ''three''');
I get an empty cursor.
If I put the sql statement in a string and revise the function like:
FUNCTION find_by_items(items IN varchar2)
RETURN SYS_REFCURSOR
AS
o_cursor SYS_REFCURSOR;
strSql varchar2(32767);
BEGIN
strSql := 'select a.something from my_table a where a.item in (' || items || ')';
open o_cursor for strSql;
return o_cursor;
END find_by_items;
And I call the function the exact same way:
select find_by_items('''one'', ''two'', ''three''');
I get the results I expect (not an empty cursor)
--
Basically I'm trying to figure out how to make the first way work, because using a string means that it's more prone to runtime errors rather than compile errors - which in my particular case is not preferable.
Thanks

The first implementation will never work as you expect, because Oracle resolves the Query as follows:
select a.something from my_table a where a.item in ('''one'', ''two'', ''three''')
and what your want is
select a.something from my_table a where a.item in ('one', 'two','three')
split and store your string parameter values into a varray so you can use the following form:
select a.something from my_table a where a.item in (table(varray_var));

Related

Return updated rows from a stored function

Im trying to select some rows from a Table in ORACLE and at the same time update the selected rows state. I found a way to do so with a stored function and Cursors but I cant manage to return the rows after using the cursor to update. This is my code:
CREATE OR REPLACE FUNCTION FUNCTION_NAME
RETURN SYS_REFCURSOR
IS
l_return SYS_REFCURSOR;
CURSOR c_operations IS
SELECT * FROM TABLE1
WHERE STATUS != 'OK'
FOR UPDATE OF TABLE1.STATUS;
BEGIN
FOR r_operation IN c_operations
LOOP
UPDATE
TABLE1
SET
TABLE1.STATUS = 'OK'
WHERE
TABLE1.ID_TABLE1 = r_operation.ID_TABLE1;
END LOOP;
COMMIT;
-- Missing conversion from cursor to sys_refcursor
RETURN l_return;
END;
The update is working but Im still missing how to return the updated rows that are in the cursor (c_operations ).
Thank you.
I'm going to make some assumptions:
id_table1 is the primary key of the table, so your RBAR (*) update affects only one row
id_table1 is numeric
If these assumptions are wrong you will need to tweak the following code.
CREATE OR REPLACE FUNCTION FUNCTION_NAME
RETURN SYS_REFCURSOR
IS
l_return SYS_REFCURSOR;
l_id table1.id_table1%type;
l_upd_ids sys.odcinumberlist := new sys.odcinumberlist();
CURSOR c_operations IS
SELECT * FROM TABLE1
WHERE STATUS != 'OK'
FOR UPDATE OF TABLE1.STATUS;
BEGIN
FOR r_operation IN c_operations LOOP
UPDATE TABLE1
SET TABLE1.STATUS = 'OK'
WHERE TABLE1.ID_TABLE1 = r_operation.ID_TABLE1
returning TABLE1.ID_TABLE1 into l_id;
l_upd_ids.extend();
l_upd_ids(l_upd_ids.count()) := l_id;
END LOOP;
COMMIT;
open l_return for
select * from table(l_upd_ids);
RETURN l_return;
END;
The key points of the solution.
uses Oracle maintained collection (of number) sys.odcinumberlist to store the updated IDs;
uses RETURNING clause to capture the id_table1 value for the updated row;
stores the returned key in the collection;
uses a table() function to casrt the collection into a table which can be queried in the ref cursor.
This last point is why I chose to use sys.odcinumberlist rather than defining a collection in the procedure. It's a SQL type, so we can use it in SELECT statements.
(*) Row-by-agonizing-row. Updating single records in a PL/SQL loop is the slowest way of executing bulk updates, and normally constitutes an anti-pattern. A straightforward set-based UPDATE should suffice. However, you know your own situation so I'm going to leave that as it is.
It looks to me like you don't need the initial cursor, since you're changing the STATUS of every row which is not 'OK' to 'OK', so you can do this is a simple UPDATE statement. Then use an OPEN...FOR statement to return a cursor of all rows where STATUS is not 'OK', which shouldn't return anything because you've already changed all the status values to 'OK'. I suggest that you rewrite your procedure as:
CREATE OR REPLACE FUNCTION FUNCTION_NAME
RETURN SYS_REFCURSOR
IS
l_return SYS_REFCURSOR;
BEGIN
UPDATE TABLE1
SET STATUS = 'OK'
WHERE STATUS != 'OK';
COMMIT;
OPEN l_return FOR SELECT *
FROM TABLE1
WHERE STATUS != 'OK'
FOR UPDATE OF TABLE1.STATUS;
RETURN l_return;
END;
Instead of a loop to update how about a bulk update collecting the updated ids. Then a table function from those returned ids.
create type t_table1_id is
table of integer;
create or replace function set_table1_status_ok
return sys_refcursor
is
l_results_cursor sys_refcursor;
l_updated_ids t_table1_id;
begin
update table1
set status = 'Ok'
where status != 'Ok'
returning table1.id
bulk collect
into l_updated_ids;
open l_results_cursor for
select *
from table1
where id in (select * from table(l_updated_ids));
return l_results_cursor;
end set_table1_status_ok;
-- test
declare
updated_ids sys_refcursor;
l_this_rec table1%rowtype;
begin
updated_ids := set_table1_status_ok();
loop
fetch updated_ids into l_this_rec;
exit when updated_ids%notfound;
dbms_output.put_line ( l_this_rec.id || ' updated.');
end loop;
close updated_ids;
end ;

Stored Procedure output result set

Apologies for the newbie question, I am writing an Oracle stored procedure that opens a cursor for a specific SQL, calculates some variables for each row returned by the cursor but the stored procedure should return as a result set these variables that have been calculated for each row returned by the cursor. I am a bit confused on how to do this - can anyone help?!
I did read some of it so far I have something like this (just a trimmed down example and not exact code) but just need to return v_calc and v_calc_res in a result set:-
CREATE OR REPLACE procedure sp_test
(
in_input in number,
out_return out sys_refcursor
)
as
v_calc number;
v_calc_res number;
CURSOR C_test IS
select blah from test where blah = in_input;
begin
open c_test
loop
fetch c_test into v_calc;
v_calc_res := v_calc*5;
end loop;
end;
If you want a procedure to return a reference cursor for the calling routine to consume the procedure itself cannot then consume it. Cursors, including reference cursors are 1 way, 1 time consumables. As for as the desired calculations, they can be added to select defined for the cursor. So:
-- setup
create table test (blah integer, blah_stuff varchar2(50) );
-- build sp
create or replace procedure sp_blah_text(
in_input in number
, out_cur out sys_refcursor
)
is
begin
open out_cur for
select blah, blah_stuff, blah*5 as blah_x_5
from test
where blah = in_input;
end sp_blah_text;
-- test data
insert into test(blah, blah_stuff)
select 1,'a' from dual union all
select 2,'b' from dual union all
select 2,'x' from dual union all
select 2,'z' from dual union all
select 3,'c' from dual;
-- test
declare
ref_cur sys_refcursor;
l_blah test.blah%type;
l_stuff test.blah_stuff%type;
l_blah_5 test.blah%type;
begin
dbms_output.enable(null);
sp_blah_text(2,ref_cur);
loop
fetch ref_cur
into l_blah
, l_stuff
, l_blah_5;
exit when ref_cur%notfound;
dbms_output.put_line('blah=' || l_blah || ',stuff=' || l_stuff || ',blah*5=' || l_blah_5);
end loop;
end;
This works a treat thank you very much. I now have a performance issue that maybe you could help with. When I open the cursor, I then run several other SELECT statements to retrieve values using the variables from the cursor (see below). I assume this is because the switch between PL/SQL and SQL engine. Would using table collections help? But as I see since I need different columns from different tables I would need to have several different collections, how could I output everything in one record?
CREATE OR REPLACE procedure sp_test
(
in_input in number
)
as
v_calc number;
v_calc_res number;
v_blah_blah number;
v_blah_blah_blah number;
v_blah_blah_blah number;
CURSOR C_test IS
select blah from test where blah = in_input;
begin
open c_test
loop
fetch c_test into v_calc;
select blah_blah into v_blah_blah from t_blah_blah;
select blah_blah_blah into v_blah_blah_blah from t_blah_blah_blah;
select blah_blah_blah_blah into v_blah_blah_blah_blah from t_blah_blah_blah_blah;
v_calc_res := v_calc*5*v_blah_blah*v_blah_blah_blah*v_blah_blah_blah_blah
end loop;
end;

Oracle - open a sys_refcursor for an explicit cursor

I have a function that returns a sys_refcursor so that a Java application can process the results:
function fn_stuff (in_array in t_id_array) return sys_refcursor is
rc sys_refcursor;
begin
open rc for
--query has been significantly shortened since most details are not
--relevant for this question.
select * from some_table where id in table(in_array);
return rc;
end;
I'd also like to be able to use this function with PL/SQL. It would be very convenient to be able to reference the rowtype of this result like this:
procedure sp_f is
rc sys_refcursor;
arr t_id_array ;
r c_stuff%rowtype;
begin
arr := t_id_array('11','22','33','44');
rc := pkg_stuff.fn_stuff(arr);
loop
fetch rc into r
exit when rc%notfound;
dbms_output.put_line(r.col1||' '||r.col2);
end loop;
end;
To do this, I'd need to declare the SELECT statement in fn_stuff as a proper cursor:
cursor c_stuff (in_array in t_id_array) is
select * from some_table where id in table(in_array);
So then I thought to update the function to simply reference the new cursor I declared:
function fn_stuff (in_array in t_id_array) return sys_refcursor is
rc sys_refcursor;
begin
open rc for
select * from c_stuff(in_array);
return rc;
end;
Which yields:
Error(52,37): PL/SQL: ORA-00904: : invalid identifier
I could keep the SELECT statement as it was inside fn_stuff but it's still in development so it would be easier to just have the query in a single place.
Is there any way to open and return a sys_refcursor on a declared cursor?
I guess another option is that I could declare a record type which has all of the columns that are selected by the query, but since the query is still under development and columns may be added ore removed from the select, the record type would have to be kept in sync. But since the sp_f procedure only is interested in a handful of columns that probably won't change, I thought the %rowtype would be better.

Run sql stored as value in a table in oracle and return recordset in SSRS report

I've a query that creates a SQL Statement as a field. I want to execute this statement and return the recordset in SSRS report.
select 'select '||FILE_ID||' FILE_ID,'||
ltrim(sys_connect_by_path('REC_FLD_'||FIELD_NUMBER||' "'||FIELD_NAME||'"',','),',')||
' from RESPONSE_DETAILS where FILE_ID=' ||FILE_ID||';'
from (select t.*,count(*) over (partition by FILE_ID) cnt from RESPONSE_METADATA t)
where cnt=FIELD_NUMBER start with FIELD_NUMBER=1
connect by prior FILE_ID=FILE_ID and prior FIELD_NUMBER=FIELD_NUMBER-1
This generates a SQL stetment - however I want this SQL to be executed.
This is an extension of this question.
I've tried to use execute immediate , cursors, dbms_sql but it does not produce output. Using it on toad. All it says is "PL/SQL procedure successfully completed"
Using the following
Declare
sql_stmt VARCHAR2(3000);
l_cursor SYS_REFCURSOR;
TYPE RefCurTyp IS REF CURSOR;
v_cursor RefCurTyp;
CURSOR c1 is
select 'select '||FILE_ID||' FILE_ID,'||
ltrim(sys_connect_by_path('REC_FLD_'||FIELD_NUMBER||' "'||FIELD_NAME||'"',','),',')||
' from RESPONSE_DETAILS where FILE_ID=' ||FILE_ID||';'
from (select t.*,count(*) over (partition by FILE_ID) cnt from RESPONSE_METADATA t)
where cnt=FIELD_NUMBER start with FIELD_NUMBER=1
connect by prior FILE_ID=FILE_ID and prior FIELD_NUMBER=FIELD_NUMBER-1;
BEGIN
open c1;
FETCH C1 into sql_stmt ;
dbms_output.put_line(sql_stmt);
close c1;
EXECUTE IMMEDIATE sql_stmt;
open v_cursor for sql_stmt;
return l_cursor;
close l_cursor ;
END;
An anonymous PL/SQL block cannot return any data to the caller. If you want to return a SYS_REFCURSOR to the calling application, you would need to create a function (or a procedure). For example
CREATE OR REPLACE FUNCTION get_results
RETURN sys_refcursor
IS
l_sql_stmt VARCHAR2(3000);
l_cursor SYS_REFCURSOR;
BEGIN
select 'select '||FILE_ID||' FILE_ID,'||
ltrim(sys_connect_by_path('REC_FLD_'||FIELD_NUMBER||' "'||FIELD_NAME||'"',','),',')||
' from RESPONSE_DETAILS where FILE_ID = ' ||FILE_ID||';'
into l_sql_stmt
from (select t.*,count(*) over (partition by FILE_ID) cnt from RESPONSE_METADATA t)
where cnt=FIELD_NUMBER
start with FIELD_NUMBER=1
connect by prior FILE_ID=FILE_ID
and prior FIELD_NUMBER=FIELD_NUMBER-1;
dbms_output.put_line(l_sql_stmt);
open l_cursor for sql_stmt;
return l_cursor;
END;
I am assuming from your code that you expect your SELECT statement to return a single SQL statement-- your code is fetching only one row from a query that potentially returns multiple SQL statements. I'm assuming that you only fetch one because you only expect the SELECT statement to return one row. Otherwise, since your query lacks an ORDER BY, you are executing arbitrarily one of N SQL statements that your code is generating.
If you are regularly going to be calling this method, you would almost certainly want to use bind variables in your dynamic SQL statement for the file_id rather than generating non-sharable SQL statements. I haven't made that change here.
There is another StackOverflow thread on calling a stored function returning a sys_refcursor from SSRS.

How to wrap an Oracle stored procedure in a function that gets executed by a standard SELECT query?

I am following these steps, but I continue to get an error and don't see the issue:
1) Create a custom Oracle datatype that represents the database columns that you want to retrieve:
CREATE TYPE my_object AS OBJECT
(COL1 VARCHAR2(50),
COL2 VARCHAR2(50),
COL3 VARCHAR2(50));
2) Create another datatype that is a table of the object you just created:
TYPE MY_OBJ_TABLE AS TABLE OF my_object;
3) Create a function that returns this table. Also use a pipeline clause so that results are pipelined back to the calling SQL, for example:
CREATE OR REPLACE
FUNCTION MY_FUNC (PXOBJCLASS varchar2)
RETURN MY_OBJ_TABLE pipelined IS
TYPE ref1 IS REF CURSOR
Cur1 ref1,
out_rec_my_object := my_object(null,null,null);
myObjClass VARCHAR2(50);
BEGIN
myObjClass := PXOBJCLASS
OPEN Cur1 For ‘select PYID, PXINSNAME, PZINSKEY from PC_WORK where PXOBJCLass = ;1’USING myObjClass,
LOOP
FETCH cur1 INTO out_rec.COL1, out_rec.COL2, out_rec.COL3;
EXIT WHEN Cur1%NOTFOUND;
PIPE ROW (out_rec);
END LOOP;
CLOSE Cur1;
RETURN;
END MY_FUNC;
NOTE: In the example above, you can easily replace the select statement with a call to another stored procedure that returns a cursor variable.
4) In your application, call this function as a table function using the following SQL statement:
select COL1, COL2, COL3 from TABLE(MY_FUNC('SomeSampletask'));
There is no need to use dynamic sql (dynamic sql is always a little bit slower) and there are too many variables declared. Also the for loop is much easier. I renamed the argument of the function from pxobjclass to p_pxobjclass.
Try this:
create or replace function my_func (p_pxobjclass in varchar2)
return my_obj_table pipelined
is
begin
for r_curl in (select pyid,pxinsname,pzinskey
from pc_work
where pxobjclass = p_pxobjclass) loop
pipe row (my_object(r_curl.pyid,r_curl.pxinsname,r_curl.pzinskey));
end loop;
return;
end;
EDIT1:
It is by the way faster to return a ref cursor instead of a pipelined function that returns a nested table:
create or replace function my_func2 (p_pxobjclass in varchar2)
return sys_refcursor
is
l_sys_refcursor sys_refcursor;
begin
open l_sys_refcursor for
select pyid,pxinsname,pzinskey
from pc_work
where pxobjclass = p_pxobjclass;
return l_sys_refcursor;
end;
This is faster because creating objects (my_object) takes some time.
I see two problems:
The dynamic query does not work that way, try this:
'select PYID, PXINSNAME, PZINSKEY from PC_WORK where PXOBJCLass ='''||PXOBJCLASS||''''
You don't need myObjClass, and it seems all your quotes are wrong.
The quoting on 'SomeSampletask'...
select COL1, COL2, COL3 from TABLE(MY_FUNC('SomeSampletask'));
Maybe I'm misunderstanding something here, but it seems like you want to be using a VIEW.

Resources